Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This reminds me of a footgun in LuaJIT's FFI: nil is generally a falsy value in Lua; a null pointer returned from a C API is equal to nil, but is not a falsy value in LuaJIT.

This means that

    if x and x == nil 
evaluates to true - a highly unusual condition in Lua. It makes sense if you think about it for a bit, but it really makes you scratch your head when you encounter this situation for the first time.


From what I've found maintaining https://github.com/Planimeter/game-engine-2d is that you should be explicitly checking for nil, separately from checking for false-evaluating values.

We found that this had the added benefit of semantically checking for something that did not exist, versus some contextual "false" value.

I suspect this is a good practice in other languages as well.


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.


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


Unity3D also introduces null-like C# objects that aren't really null, for wrapping C++ objects that can disappear behind C#'s back. This is related to Unity's own C++ object wrappers derived from UnityEngine.Object, not C#'s standard P/Invoke FFI.

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 (like GameObject) that override == and != to act like they're null in some cases, but report a different 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 that's the root of the problem, 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.

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 underlying C++ object it points to has been destroyed.)

It gets even more confusing when you mix it with coroutines and C# native implicit null checking operators ?. and ??.

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

>Fake-null objects are an important requirement due to the fact that Unity is a C++ engine with explicit memory management and C# / .NET is a managed environment. Objects derived from UnityEngine.Object have an actual partner on the native code side. You can not destroy objects explicitly in the managed world. However we have the Destroy method to destroy any objects derived from UnityEngine.Object. What the method does is actually destroying the object on the native side and mark the managed wrapper object as "being destroyed". Such an object will essentially pretend that it is null since it's no longer useable since the native counterpart is missing. That fake null object will eventually be garbage collected once all references to it are gone.

Here's a good explanation from Lucas Meijer at Unity:

https://blog.unity.com/technology/custom-operator-should-we-...

>Custom == operator, should we keep it, by Lucas Meijer.

>[...] Going over all these upsides and downsides, if we were building our API from scratch, we would have chosen not to do a custom null check, but instead have a myObject.destroyed property you can use to check if the object is dead or not, and just live with the fact that we can no longer give better error messages in case you do invoke a function on a field that is null.

>[...] We're a bit nervous about that, as if you haven't read this blogpost, and most likely if you have, it's very easy to not realise this changed behaviour, especially since most people do not realise that this custom null check exists at all.[3] [...] This led to the "well if even we missed it, how many of our users will miss it?", which results in this blogpost :)

It's a common problem raised on the forums:

https://answers.unity.com/topics/missingreferenceexception.h...

https://answers.unity.com/questions/1705335/how-can-throw-mi...

https://stackoverflow.com/questions/58607172/missingreferenc...

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

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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: