We're working on something new! Hook Relay gives you Stripe-quality webhooks in minutes. Sign up for free today!

Rustic Nil Handling in Ruby

Ad-hoc nil usage causes a lot of problems in Ruby. In this post we explore a more explicit way to handle nil conditions, inspired by Rust.

For the past six months or so I've been working an NES emulator in Rust. As you might expect, I've learned a lot about rust, and even more about NES internals. But the experience has also changed the way I look at Ruby.

Specifically, it's made me more than a little paranoid about methods that return nil.

If You Don't Stand for Something, You'll Fall for Anything

What does nil mean in Ruby? Almost anything. When a method returns nil, it could mean:

  • The method has no return value
  • There's usually a return value but not this time
  • It returns a value from the database, which is NULL
  • Something unexpected happened

This makes code hard to read and is the main cause of the most common Ruby exception in Ruby: NoMethodError. As part-owner of an exception monitoring service, NoMethodError is putting my kid through school.

Look at the following code. It returns nil most of the time because if statements evaluate to nil when the conditional doesn't match and there's no else.

def color_name(rgb)
  if rgb == 0x000000

=> NoMethodError: undefined method `titleize' for nil:NilClass

If you're an experienced Ruby developer, you know this rabbit hole goes much deeper. Sometimes these different meanings of nil overlap in strange ways, making it impossible to know whether — for example — a value in a database is NULL, or there's no value in the database at all.

A Better Way

In Rust there's no such thing as nil. Instead, when we want to signify that a function sometimes returns a value and sometimes returns "nothing" we use an Option.

Options are a type that either contains some specific value, or contains no value. Here's what they look like in code:

Option::Some(42); // Wraps the number 42 in an option
Option::None;     // Indicates "no result"

This is already looking better than our ad-hoc nil usage, but it gets even better. The Rust compiler forces you to consider the None case. You can't accidentally ignore it.

match my_option {
  Some(x) => do_something_with_x(x),
  // If you remove the `None` match below, this code
  // won't compile.
  None => do_the_default_thing()  

So we could write our color naming example in rust like so:

fn color_name(rgb: u32) -> Option<String> {
    if rgb == 0x000000 {
    } else {

Now we're forced to handle both the Some and None conditions:

let name = color_name(0xFFFFFF);

let name = match color_name(0xFFFFFF) {
  Some(value) => value,
  None => "unknown".to_owned(),

Sure this is a little verbose and weird looking, but it makes it impossible to ignore the case when a function doesn't return a useful value. That means code that's easier to understand and maintain.

Implementing Option in Ruby

What Ruby lacks in rigor it makes up for in flexibility. I thought it'd be interesting to try to implement something like Option in Ruby.

We can't create compile-time errors in Ruby, since it's an interpreted language. But we can cause incorrect code to always raise an exception, instead of only raising an exception when you hit an edge case.

First, let's create two classes. Some holds a read-only value. None is empty. They're as simple as they seem.

  class Some
    attr_reader :value
    def initialize(value)
      @value = value

  class None

Next, we'll create our Option class which holds either Some or None and only lets us access them when we provide handlers for both.

class Option
  def initialize(value)
    @value = value

  def self.some(value)

  def self.none()

  def match(some_lambda, none_lambda)
    if @value.is_a?(Some)
    elsif @value.is_a?(None)
      raise "Option value must be either Some or None"

Finally, we can rewrite our color example to use the new Option class:

def color_name(rgb)
  if rgb == 0x000000

puts color_name(0x000000).match(
  -> value { value },
  -> { "no match" })

# Prints "black"


I've yet to try this technique in a real project. I think it could definitely prevent a lot of the NoMethodErrors that always slip into production. It is a bit cumbersome looking, and not very Rubyish but I imagine that with some refinement a more pleasant syntax would emerge.

Honeybadger has your back when it counts.

We're the only error tracker that combines exception monitoring, uptime monitoring, and cron monitoring into a single, simple to use platform. Our mission: to tame production and make you a better, more productive developer.

Learn more
author photo

Starr Horne

Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

More articles by Starr Horne
“We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release.” 
Michael Smith
Try Error Monitoring Free for 15 Days