Ruby's Conditional Send is not Safe Navigation

tl;dr: Ruby has a feature called “conditional send” which looks very similar to JavaScript’s “safe navigation” feature. Their key difference is short circuiting evaluation: Ruby chooses not to short circuit, because there are methods on nil, unlike undefined in JS.

Let’s dive into an example. You can do this in Ruby:

invoice&.amount

which calls .amount if invoice is not nil, or else evaluates to nil (skipping the call). You can do something similar in JavaScript, just with ? instead of &:

invoice?.amount

In my experience, most people first learn this feature in JavaScript, where it’s called “safe navigation,” and they bring the name with them when learning Ruby.

In fact, the feature Ruby has is not safe navigation but something else, called conditional send.

There’s a subtle difference, only noticeable in a longer chain:

invoice?.amount.format()
invoice&.amount.format()

To completely reproduce the JavaScript behavior in Ruby, we need to keep chaining the &:

invoice&.amount&.format

Most Ruby linters will check for this and require programmers to put & on all method calls downstream of the first conditional send.

Why the difference? Or rather: why doesn’t Ruby do what JavaScript does?

In JavaScript it never makes sense to access a property on undefined: undefined does not have any properties, nor is it possible to add any.

But in Ruby, nil inherits from Object (and thus Kernel and BasicObject), which means it has all the methods available to all objects. For example:

But even more: Ruby lets users monkey patch more methods onto nil. Two common methods available on nil in codebases using Rails are blank? and present?.

So Ruby chooses not to short circuit. There are enough methods available on nil that it would be too restrictive to unconditionally short circuit and not allow any further method calls.

The names of both features reflect this choice:

Inside the Ruby VM, Sorbet, and Rubocop rules, the AST node representing x&.foo is named “csend.” And now you know why.