2

What is the correct way to robustly test for null in F#?

I have a mixed F#/C# project built on top of the Unity game engine (which is a closed source mono c# / c++ engine).

I have a F# function that calls a Unity API function that may return null. The Unity function returns null but my F# code fails to detect this (I know this both from the shape of the test data, attaching a debugger, inserting log statements etc). Every null test I write seems to returns false when it should be true. First try:

let rec FindInParents<'t when 't : null> (go : GameObject) = 
    match go with 
    | null -> null
    | _ ->
        let comp = go.GetComponent<'t>() // GetComponent returns null
        match comp with
        | null -> FindInParents (go.transform.parent.gameObject) // This should be matched but isn't
        | _ -> comp // Always this branch

I have also tried the following without success:

let rec FindInParents<'t when 't : null> (go : GameObject) = 
    if obj.ReferenceEquals (go, Unchecked.defaultof<'t>) then null 
    else 
        let comp = go.GetComponent<'t>() // Comp is null
        if obj.ReferenceEquals (comp, Unchecked.defaultof<'t>) then FindInParents<'t> (go.transform.parent.gameObject)
        else comp // Always this branch

I feel like I'm missing something fundamental here but so far it has eluded me. Any pointers?

Edit: I should also point out that GetComponent always returns a subtype of UnityEngine.Component and is always a reference type. UnityEngine.Component is a subtype of UnityEngine.Object which defines a custom == operator (I don't think this is relevant though because == is should not be called in the second example (see Daniel's answer to [Handling Null Values in F#)

Community
  • 1
  • 1
Ben.J
  • 41
  • 4
  • [Those should work.](https://dotnetfiddle.net/8iogbq) Something else is going on. – Daniel Jan 23 '15 at 23:05
  • "*What is the correct way to robustly test for null in F#?*" See [this question and answers](http://stackoverflow.com/a/11696947/636019). Your second approach looks just fine. – ildjarn Jan 23 '15 at 23:24

1 Answers1

2

Turns out that Unity uses a fake null value for objects that have been destroyed on the unmanaged side but not yet been collected on managed side. Custom == / != operators check for fake null values.

For generic functions like the ones in the question F# will uses an IL instruction for null testing (brfalse.s) - this will obviously not detect Unity fake nulls. Testing for null explicitly results in a call to LanguagePrimitives.HashCompare.GenericEqualityIntrinsic which also has no knowledge of Unity fake nulls.

The solution is to call Equals on the unity object to ensure that the overloaded Unity operator is called:

let isUnityNull x = 
    let y = box x // In case of value types
    obj.ReferenceEquals (y, Unchecked.defaultof<_>) || // Regular null check
    y.Equals(Unchecked.defaultof<_>) // Will call Unity overload if needed
Ben.J
  • 41
  • 4