Although F# largely eliminates null, there are still several ways to get a NullReferenceException using F#, some of which may be surprising. But let’s start with the obvious ones:
- Unchecked.defaultof. This has an intentionally ugly name with “Unchecked” in it and its whole purpose is to create default values, i.e. null for reference types.
Advice: Use “Unchecked.defaultof” as you would “unsafe” in C#: only in specific interop or performance scenarios and when you really know what you’re doing.
- Array.zeroCreate. This creates an array where every value is Unchecked.defaultof of the specified type, akin to doing “new T[x]” in C#.
Advice: Write the code in such a way that you can create and initialize the array at the same time, using Array.create, Array.init or expressions like array comprehensions. Only use zeroCreate when you absolutely must defer member initialization and cannot afford to fill the array with some temporary values, e.g. in performance-critical code.
- Declaring explicit fields with val. Fields declared in this can be temporarily uninitialized and therefore null.
Advice: As MSDN says: “Explicit fields are not intended for routine use. In general, when possible you should use a let binding in a class instead of an explicit field. Explicit fields are useful in certain interoperability scenarios, such as when you need to define a structure that will be used in a platform invoke call to a native API, or in COM interop scenarios.”
So far all these are language features designed for interop or high-performance scenarios where you explicitely opt-in to allowing nulls. But you should also be aware of nulls that can creep up in more ordinary F# code:
- Option.Value. “Get the value of a Some option. A NullReferenceException is raised if the option is None.”
Advice: Don’t use it! New F# programmers, particularly coming C#, may want to use Option as they would Nullable in C#: testing for and then accessing the value using the associated property. I’m not entirely sure why the member is visible from F#, as it should only be used by the compiler and perhaps other languages. F# gives you pattern matching, use it.
// Poor style in F#, // throws NullReferenceException if the conditional logic is wrong if myOption <> None then doSomething(myOption.Value) // Fool-proof match myOption with | Some(value) -> doSomething(value) | None -> ()
- Using instance methods ToString() or GetHashCode() in generic code. A subtle point of F# is that both the Unit type and Option.None are represented as null in compiled code. It is therefore unsafe to use instance methods on generic method arguments that could be either Unit or Option.None.
Advice: Use string and hash instead of ToString() and GetHashCode(). To illustrate:
// Throws NullReferenceException if value is () or None let printHashCode value = printf "%d" (value.GetHashCode()) // Safe in all cases let printHashCode value = printf "%d" (hash value) printHashCode ();; printHashCode None;;