News for hackers. Stuff that matters.
Re:When nil is not nil by strait on Monday October 10, 2022 @04:54PM (#33156282)
When does

    if x and x == nil
ever evaluate to true in LuaJIT? What does the FFI have to do with it? Please explain.

Edit:

I think I see what you mean here. The x variable is actually a CDATA object returned by the LuaJIT FFI. Upon comparison, the NULL pointer value inside would be converted to nil. I don't see this as being a 'footgun' unless you forget that you're dealing with a CDATA object.

Re:When nil is not nil by elpocko on Monday October 10, 2022 @05:27PM (#33156572)
A null pointer is a boxed value in LuaJIT, therefore not nil. But it is equal to nil when compared with nil.

    local x = require('ffi').cast('void*', nil)
    if x and x == nil then
        print("x is truthy and nil at the same time")
    end
Re:When nil is not nil by DonHopkins on Tuesday October 11, 2022 @04:10AM (#33160643)
Unity3D also introduces null-like C# objects that aren't really null, for wrapping C++ objects that can disappear behind C#'s back.

So of course standard C# can report a NullReferenceException if you try to do something with an actual "null" value, but in Unity3D you can also have object references to C++ object wrappers that override == and != to act like they're null in some cases, but report MissingReferenceException if you try to do other things with them. And that makes comparing Unity C++ objects to null much slower, since it has to thunk into native code to perform the comparison, not just use one machine instruction.

This causes a some very subtle bugs (and performance problems) because many people don't actually understand what's really going on behind the scenes. They don't realize there's another level of indirection and overriding going on, and just brute-force a solution by checking for null more often, without realizing the people getting confused really do have a valid reason for their confusion and aren't just sloppy programmers.

Notice how some of the forum replies in the links below just mansplain to the people reporting problems that they "simply" need to check for null harder as if they were dummies, without mentioning Unity's obscure == and != C++ object wrapper overriding hack, even though they did check for null (i.e. before calling a coroutine, which was too early), but it unexpectedly changed out from under them. This is a classic leaky abstraction and foot gun.

https://blog.theknightsofunity.com/story-nullreferenceexcept...

>But Unity objects are a bit different. Unity objects can report themselves as null even if they are still referencing an object!

>No, that’s not a bug! Some referenced objects are scene objects. Imagine what happens if a scene is being replaced by another scene – all regular scene objects have to be destroyed. Since Unity cannot update your valid references inside your code (they still will be valid after destroying the scene), it is faking a null check for those that you are still referring to, so it will (should) stop you from use these any longer. In case you do try to use any of these objects, you may receive a message like this one:

>MissingReferenceException: The object of type 'Texture2D' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object.

https://forum.unity.com/threads/missingreferenceexception.17...

    Code (CSharp):
    public class TestPeriodic : MonoBehaviour
    {
        private void Start()
        {
            FindObjectOfType<GameplayManager>().Register(this);
        }

        public IEnumerator TheTick()
        {
            var i = 0;

            // while (gameObject)
            // while (!(gameObject is null))
            while (gameObject != null)
            {
                i++;
                Debug.Log($"Main Ticker: {i}");
                yield return new WaitForSeconds(1f);
            }

            Debug.Log("Destroyed, goodbye.");
        }
    }

    public class GameplayManager : MonoBehaviour
    {
        public void Register(TestPeriodic testPeriodic)
        {
            StartCoroutine(testPeriodic.TheTick());
            StartCoroutine(TheAsyncLoad(testPeriodic));
        }

        private static IEnumerator TheAsyncLoad(Object testPeriodic)
        {
            yield return new WaitForSeconds(2f);
            Destroy(testPeriodic);
        }
    }

    // ======================
    // ERROR
    // ======================
    //
    // MissingReferenceException: The object of type 'TestPeriodic' has been destroyed but you are still trying to access it.
    // Your script should either check if it is null or you should not destroy the object.
    // TestPeriodic+<TheTick>d__1.MoveNext () (at Assets/TestPeriodic.cs:17)
    // UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <fe84f4a754da4a6bb64fca409d40938a>:0)
So you can have a non-null reference in a variable, that apparently changes to a (virtual) null behind your back, without you ever changing the value of the variable! Here's an example of somebody who was confused by that, because they checked for null BEFORE entering a co-routine, and then even though they never reassigned the value of the variable in the co-routine, the GameObject that it referred to got destroyed, morphing the C++ GameObject wrapper into a virtual null reference behind their back. (i.e. an actual C# wrapper object that lies that it's equal to null, because the C++ object it points to has been destroyed.)

https://forum.unity.com/threads/missingreferenceexception-i-...

Also note that null comparisons against Unity objects are considered "expensive" because of this hidden overriding of the equality operator.

https://blog.jetbrains.com/dotnet/2019/02/21/performance-ind...

https://github.com/JetBrains/resharper-unity/wiki/Avoid-null...

>Avoid null comparisons against UnityEngine.Object subclasses

>Classes deriving from Unity.Object inherit equality operators that change the behaviour of the == and != operators. While these operators will perform standard .NET reference equality, if comparing one side to null, these operators will call native code to check if the underlying native engine object is still alive. For more details on the background of this, please see the explanation of the "Possible unintended bypass of lifetime check of underlying Unity engine object" inspection.

>This transition to native code can be expensive, as Unity will perform lookups and validation to convert the script reference to the native reference. While small, the cost of comparing a Unity object to null is much more expensive than a comparison with a plain C# class, and should be avoided inside a performance critical context, and inside loops.

>This inspection will add a performance indicator highlight to null comparisons against UnityEngine.Object subclasses inside a performance critical context. It will also provide the following Alt+Enter context actions:

>Move outside loop. This will introduce a variable to hold the result and move the comparison outside the scope of a loop. Move to Start or Awake. This will introduce a field to hold the result and move the comparison to Start or Awake. This inspection was first added in Rider/ReSharper 2018.3

https://github.com/JetBrains/resharper-unity/wiki/Possible-u...

>Possible unintended bypass of lifetime check of underlying Unity engine object

>This is a Unity specific inspection. It only runs in a Unity project.

>This warning is shown if a type deriving from UnityEngine.Object uses either the null coalescing (??) or null propagation or conditional (?.) operators. These operators do not use the custom equality operators declared on UnityEngine.Object, and so bypass a check to see if the underlying native Unity engine object has been destroyed. An explicit null or boolean comparison, or a call to System.Object.ReferenceEquals() is preferred in order to clarify intent.