Skip to content

Null, Nil, and UnityEngine.Object

Tims Gardner edited this page Dec 3, 2018 · 3 revisions

In addition to Clojure's built-in nil? function, Arcadia introduces arcadia.core/null? and arcadia.core/null->nil. These require some explanation beyond their docstrings.

C# supports operator overloading, the ability to modify the behavior of operators such as == depending on the static types of the arguments. Crucially, this overload is early bound: it will only take effect if the overloaded types of the arguments are statically known. That is, the expression

x == y

may well evaluate to a different value depending on how x and y are type annotated, even for the same runtime values of x and y. In other words, it is an inherently static feature of C#, which presents some awkwardness for a dynamic language like Clojure.

Unity uses operator overloading to emulate the behavior of null for destroyed UnityEngine.Object instances. Say x is, in fact, a GameObject instance (a subclass of UnityEngine.Object):

GameObject x = new GameObject();

If x has not been destroyed, the following will evaluate to false, and if it has been destroyed, to true:

x == null

However, the following will evaluate to false regardless of whether x has been destroyed or not:

(System.Object x) == null

This behavior and its drawbacks are described in more detail here. According to the same blog post,

...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

Unfortunately, we seem to be stuck with this behavior.

nil is a very important value in Clojure. Clojure does not respect Unity's "fake" null behavior. For any runtime value x that is, in fact, a UnityEngine.Object, rather than nil, the Clojure code

(nil? x)

will evaluate to false.

Arcadia introduces arcadia.core/null? and arcadia.core/null->nil to bridge these two systems.