Ruby's or-equals operator,
||=, is deceptive. It has a common idiomatic use case in Ruby: setting defaults, but comes with some nasty suprises up its sleeve.
Let's say you're writing a class to send emails and you want to have a default reply-to email address, unless one has been specified.
You might write this logic out as:
if reply_email.nil? reply_email = "[email protected]" end
Making use of the or-equals operator, a more succinct way of writing the same code would be:
reply_email ||= "[email protected]"
Given this behavior, we might hypothesize that the or-equals operator is shorthand for:
x = x || y
But we'd be wrong!
In fact, the or-equals operator behaves more like:
x || x = y
where the variable assignment only occurs if x is falsey (i.e.
nil). If x is truthy (i.e. anything besides
false, yes even
0), then the logic is short-circuited at the
|| and no variable assignment occurs.
Why does this matter?
This might seem like a trivial distinction; both of these logical statements will have the same outcome under many conditions. However, there are a handful of edge cases that will produce slippery bugs in your code if you're not careful.
For example, the not-so-edge case of booleans.
Let's try assigning default boolean values works with the or-equals operator...
x ||= true #=> true x = false x ||= true #=> true
WTF?! We already assigned a value of
x. Why is it returning
This is because the value
false is not short-circuiting the logic of the or-equals operator. Instead, it is allowing it to proceed to the assignment part of the logic, setting
So if we can't use
||=, what is the 'Ruby way' of setting default boolean values?
This is the most succinct way I've found so far:
defined?(x) or x = true
Unfortunately, this way requires us to state the variable twice and repetition makes our code more brittle when refactoring. But it seems there doesn't exist a snappy way to set boolean variables in Ruby, so until the underlying logic of the or-equals operator is revised, we're stuck with more verbose declarations of default boolean values.
For more tricky edge cases, check out http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html to get a more in-depth look at the or-equals operator in Ruby.
Thread safety and ||=
Another reason the or-equals operator is not always your friend is that it's not thread safe when used for memoization. This means that if multiple threads access the same Ruby code in memory at the same time, they might get different values for a variable, resulting in tricky bugs and unpredictable behavior.
Thread safety doesn't apply to everyone, but if you're using threaded libraries like Sidekiq, a threaded web server like Puma, or considering switching from the standard MRI Ruby to threaded implementations of Ruby such as JRuby or Rubinius,
this post on writing thread-safe code with Ruby is a good read.