The concept of null is considered as the billion dollar mistake and I very much agree. The defects caused by it is even more apparent in games where state changes happen a lot. In a complex simulation game like ours, we encounter a lot of NullPointerException (NPE) that we can’t trace how it happened. It’s not supposed to happen but it did.

We have assertions in place to let us know that if it didn’t pass, it’s considered as a bug. We even enable it on the release build so it will be logged during bug reports. Most of these are asserting something to be not null. However, when these assertions do fail or NPEs happen, the game still continues to run on a broken state. This is when I’ll have a mild panic attack. My usual thought is “this may have caused more bugs down the line then the player left a negative review”. There’s got to be a better way to deal with nulls.

How other languages deal with it

Everyday, I try to give 30 minutes to an hour to study/try a new technology. Last month, I happen to dabble with Rust. I wanted to know what the fuss is about. Why is it such a beloved language? I happen to fall in love with it, too. I like statically typed languages due to its constraints and strictness. It makes code more maintainable. Rust takes this even further. It adds more constraints, especially in memory access and checks them during compilation. I even wished that Unity used Rust for scripting instead. But that’s not going to happen in a million years.

I also found in Rust how they handle null. They don’t have it. Instead, you have to use Option<T> to support the concept of none or nothing. Option<T> is an enum that has two values – Some and None. Enums in Rust can have data/state with them. If you have a variable of type Option<T> and it has a value, you use the Some value. You use None if it doesn’t have a value. It’s as if you’re assigning null to it.

// These are two variables in Rust let some_string: Option<String> = Some(String::from("hello string")); let null_string: Option<String> = None; // This is like assigning null

The difference in Rust is how you access the value stored in the Option<T> variable. It doesn’t have like a Value property or get_value() method. You can only access the value using pattern matching.

// There's no such thing in Rust let string_from_option: String = some_string.get_value(); // To get the string value, you have to use pattern matching match some_string { Some(value) => { // Do something with the String value here }, None => { // Do something when it has no value } }

Why is this better? It’s better because you’re forced to handle the non existence of the value when dealing with an Option<T> variable. There’s no other way around it. It’s a very clear intent: “The variable may not have a value. You should handle it.” This helps reduce NPEs. So I thought, “how can I port this to C#?”

Implementation

Turns out that there are already C# libraries for this like this one. I didn’t like it though because it uses anonymous delegates which means it throws garbage whenever it is invoked. That’s not good for games. So I tried to come up with my own.

public struct Option<T> where T : class { public static readonly Option<T> NONE = new Option<T>(); public static Option<T> Some(T value) { return new Option<T>(value); } private readonly bool hasValue; private readonly T value; private Option(T value) { this.value = value; Assertion.AssertNotNull(this.value); // Can't be null this.hasValue = true; } public bool IsSome { get { return this.hasValue; } } public bool IsNone { get { return !this.hasValue; } } public void Match(IOptionMatcher<T> matcher) { if (this.hasValue) { matcher.OnSome(this.value); } else { matcher.OnNone(); } } public TReturnType Match<TReturnType>(IFuncOptionMatcher<T, TReturnType> matcher) { return this.hasValue ? matcher.OnSome(this.value) : matcher.OnNone(); } } public interface IOptionMatcher<in T> where T : class { void OnSome(T value); void OnNone(); } public interface IFuncOptionMatcher<in TWrappedType, out TReturnType> where TWrappedType : class { TReturnType OnSome(TWrappedType value); TReturnType OnNone(); }

These are the main components of my Option framework. Option basically is a struct wrapper to a class pointer/reference. The NONE static value can be used as the none or nothing state. The static method Some() is used to create an Option that has a value.

Then it has a method Match() that accepts an interface. The method OnSome() is the only way that the value inside Option can be accessed. Note that there’s no Value property or GetValue() in Option struct.

Why interface? It’s because I could then create a common implementation like this:

public class DelegateOptionMatcher<T> : IOptionMatcher<T> where T : class { public delegate void SomeProcessor(T value); public delegate void NoneProcessor(); private readonly SomeProcessor someProcessor; private readonly NoneProcessor noneProcessor; public DelegateOptionMatcher(SomeProcessor someProcessor, NoneProcessor noneProcessor) { this.someProcessor = someProcessor; this.noneProcessor = noneProcessor; } public DelegateOptionMatcher(SomeProcessor someProcessor) : this(someProcessor, null) { } public void OnSome(T value) { this.someProcessor(value); } public void OnNone() { this.noneProcessor?.Invoke(); } }

This is how it used:

// Say we have this as a member variable private IOptionMatcher<Character> dealDamageMatcher; public void ApplyDamage(Option<Character> target) { if(this.dealDamageMatcher == null) { this.dealDamageMatcher = new DelegateOptionMatcher(delegate(Character target) { // This is only executed if there is indeed a target target.DeductHp(this.damageAmount); }, delegate() { // Code here when target is none }); } target.Match(this.dealDamageMatcher); }

This technique allows me to write the code that I want executed if the value is not null without having to create a new class type. This also avoids garbage as the delegates are stored in an instance that is then reused in subsequent calls.

A common implementation for IFuncOptionMatcher<T, R> is also written in the same way but with a return type.

Issues

One glaring issue with this is verbosity. Generally, I don’t have a problem with it as long as the intent is clear. But it can be frustrating to have to create an IOptionMatcher<T> instance just to handle a value from an Option. Basically, I’m paying safety with verbosity.

Another issue is dealing with outside variables. A solution like DelegateOptionMatcher is not enough when you have external variables that you need to use inside the handler. Consider the following:

private Option<Weapon> currentWeapon; private IOptionMatcher<Weapon> setMultiplierMatcher; public void SetDamageMultiplier(float amount) { if(this.setMultiplierMatcher == null) { this.setMultiplierMatcher = new DelegateOptionMatcher(delegate(Weapon weapon) { weapon.DamageMultiplier = amount; }); } this.currentWeapon.Match(this.setMultiplierMatcher); }

Can you find the bug? The variable amount here is external to the matcher. When the DelegateOptionMatcher is first instantiated, it records the value of amount only at that moment. When the method SetDamageMultiplier() is invoked again, the value of the passed parameter will not be the one that will be used in the matcher. It will use the value when it was instantiated.

For example, on first call say SetDamageMultiplier(1.1f). The value 1.1 will be stored in the instantiated matcher. On next call say SetDamageMultiplier (2.0f), the amount used in the matcher would still be 1.1. Bummer, right?

To fix this, I have to write an inner class like this (which I was trying to avoid):

private class SetDamageMultiplierMatcher : IOptionMatcher<Weapon> { private int amount; public void Init(int amount) { this.amount = amount; } public void OnSome(Weapon weapon) { weapon.DamageMultiplier = this.amount; } public void OnNone() { // Nothing to do here } } private readonly SetDamageMultiplierMatcher setMultiplierMatcher = new SetDamageMultiplierMatcher(); public void SetDamageMultiplier(float amount) { this.setMultiplierMatcher.Init(amount); this.currentWeapon.Match(this.setMultiplierMatcher); }

This is just ugly.

Conclusion

I’m using this concept on some of our critical parts, the ones where we get the most NPEs. It’s not the best solution but it’s the best I got for now. If you know something better or how to make it better, do tell. I’m all ears.

How I wish C# won’t allow direct access to the value if a variable can be null. The programmer should be forced to handle it via a code structure like pattern matching. Any other way works for me.

I also know that in C# 8, there’s an option that you can set where references are non nullable by default. But still, direct access to nullable variables are still allowed via the Value property. Either way, C# 8 for Unity will only be available by 2020. By then, more NPEs will have propagated.