17 February 2025

Hello [null]

Clouds and mist around the shadows of a mountain

In summary

  • null is the bane of software engineering and has been called the “billion dollar mistake”
  • Many programming languages fail to alert the developer to potential null values, leaving the program vulnerable to runtime crashes
  • Borrowing from functional programming can help mitigate issues with null values

An introduction to options

In functional programming, an option is a special type used to represent data that may have missing values. To understand how options can help with null issues, we first need to understand a bit more about null.

In programming, null is a special term that effectively represents the lack of a value. Unfortunately, it can often be problematic, as attempting to use null as a value where you are expecting data to exist will typically cause your program to crash. Because of this, it is often stated that null pointers are a billion dollar mistake, and I completely agree with that assertion. However, the lack of a value is valid in many cases, so how can we represent this without all of the pain that comes with null?

Before I answer that, let’s understand the state of things in most languages:

  • Typically, there is some special syntax that denotes a type, T, may be null.
  • Within Python, use the typing.Optional[T] type, or the newer union syntax T | None.
  • In C#, opt into the nullable suffix T?.
  • For other languages such as Go, use a pointer to represent an optional value with *T.

I find all of these quite manageable. C# will give you warnings if you haven’t checked that a value is present, either with the nullable operator or an explicit if check. Static analysis tools like mypy for Python tend to do a good job of this too, though it isn’t built in to the language itself. Go also has similar tools like golangci-lint to pick up the slack where the compiler doesn’t check.

Nullable values in practice

Let’s see what these look like in practice, without any 3rd party tools or configuration.

Python will crash at runtime:

name: str | None = None
name.upper()

# AttributeError: 'NoneType' object has no attribute 'upper'

C# gives a compiler warning when opting into the nullable suffix, but will not prevent you from crashing your program at runtime.

string? name = null;
name.ToUpper();

// Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

Go will crash at runtime, with no warning about a potential nil dereference.

import "strings"

var name *string = nil
strings.ToUpper(*name)

// panic: runtime error: invalid memory address or nil pointer dereference

The alternative

Given that many languages require either some level of configuration or a 3rd party tool to help with null checking, what if it was impossible to miss a potential null value?

Let me introduce the Option (or Maybe) type from our friends over in the functional programming world. This is a special type used to represent data that may have missing values. Option has two possible states, which represent whether a value exists, or not.

  • Some(T) and
  • None

How are they different to nullable values?

Nullable values can often be incorrectly accessed, and with no warnings or errors from the compiler until your program crashes. With an Option, the value you have is wrapped in another type, so you are unable to access the inner value without either checking the value exists, or explicitly pulling the value using something like Rust’s Option::unwrap, which will crash the program when the value is not present. I personally prefer this as the explicitness makes it extremely obvious as to what is happening when reading and writing code.

Let’s revisit at the first example again, but with a language that implements the Option type, Rust.

let name: Option<&str> = None;
name.to_uppercase();

// error[E0599]: no method named `to_uppercase` found for enum `Option` in the current scope

The compiler fails citing that to_uppercase doesn’t exist on the Option type. This means we cannot accidentally directly access the underlying value in an Option in the same way that you can dereference a null pointer.

Using an Option

Since we can’t just use T when it’s wrapped in an Option, we must first unwrap T. There are various methods to do so, which you choose will depend on the situation.

You can use Option::unwrap in Rust’s implementation, which will take the inner value, but crash at runtime if that value is None. This can be mitigated using calls to Option::is_some or Option::is_none to help with control flow, but there are better ways.

Rust in particular offers if let expressions, that allow you to assign a variable and execute a block of code if a condition matches. The below will run n.to_uppercase() if name has a value.

let name: Option<&str> = None;

if let Some(n) = name {
  n.to_uppercase();
}

Options also feature a lot of helper methods, such as map, can be used to apply a function to the Some(T) variant of an Option, or unwrap_or, which will give you the current value in the Option, or the provided default value if the Option is None.

let name: Option<&str> = None;
let mapped: String = name.map(str::to_uppercase).unwrap_or("No name provided".into());

The Python equivalent for this code would be.

name: str | None = None
mapped: str = name.upper() if name is not None else "No name provided"

While the Python version is more concise, there is nothing built in to warn you that you’d even need the if ... in the first place.

So, why use an Option?

Options require you to access the value through verifying its state, or explicitly pulling the value and accepting a program crash if it is not there. This means compilers are able to catch misuse much easier than nullable types or pointers, leading to safer and more reliable code overall.

Get in touch

Get in touch with Louder or sign up to Louder’s newsletter to receive our articles and the latest industry updates straight to your inbox.



About Sam Kenney

Sam is a data engineer. In his spare time he plays guitar for the UK-based alternative band, Worst Case Scenario.