What Ruby’s ||= (Double Pipe (original) (raw)

In Rubyists Already Use Monadic Patterns, Dave Fayram made a passing reference to using ||= to set a variable's value if its value were 'Nothing' (false or nil in Ruby). The resulting Reddit quickly picked up on his definition (which was fixed later) and argued about ||='s true meaning which isn't as obvious as many Rubyists think. This spread to Freenode's awesome #ruby-lang IRC channel where I picked it up.

Abstract (or the TLDR Version)

A common misconception is that a ||= b is equivalent to a = a || b, but it behaves like a || a = b

In a = a || b, a is set to something by the statement on every run, whereas with a || a = b, a is only set if a is logically false (i.e. if it's nil or false) because || is 'short circuiting'. That is, if the left hand side of the || comparison is true, there's no need to check the right hand side.

The Common Misconception

a ||= b being equivalent to a = a || b is a popular interpretation for two reasons:

  1. If a and b are both local variables, a = a || b is a short and natural reflection of the outcome.
  2. Other operators like += and -= do operate this way (and this standard dates back to C), e.g.: a += b is equivalent to a = a + b

Do not confuse [op]= with anything related to ||= or &&=. They're entirely different ideas and are implemented entirely different[ly].

Evan Phoenix (of Rubinius fame)

What's happening then, if not a = a || b?

A Starter for One

Here's a simple example of using a ||= b:

a = nil b = 20 a ||= b a # => 20

In this case, a ||= b seems to behave like a = a || b. As mentioned earlier, this is entirely due to a and b both being local variables.

Full Demonstration for Hashes and Arrays

Let's try something more complicated:

h = {}

def h.[]=(k, v) puts "Setting hash key #{k} with #{v.inspect}" super end

1. The standard ||= approach

h[:x] ||= 10 h[:x] ||= 20

2. The a = a || b approach

h[:y] = h[:y] || 10 h[:y] = h[:y] || 20

3. The a || a = b approach

h[:z] || h[:z] = 10 h[:z] || h[:z] = 20

The output:

Setting hash key x with 10 Setting hash key y with 10 Setting hash key y with 10 Setting hash key z with 10

Note that in the first case, using ||=, the hash key's value is only set once. Once it becomes logically truthful (i.e. anything other than nil or false), h[:x] is no longer assigned any new values, not even itself.

The second case, using the a = a || b approach, does result in two assignments (of the same value). The value remains 10 but the syntax forces h[:y] to assign itself as a value again.

In the last case, the behavior is the same as in the first case, demonstrating that a || a = b is a more realistic notation.

Note: Exactly the same result occurs if we switch the hash for an array and the keys for integers.

Full Demonstration for Getter/Setter Methods

A similar outcome occurs if we're referring to objects with getter/setter methods (which you may call accessors):

class MyClass attr_reader :val

def val=(val) puts "Setting val to #{val.inspect}" @val = val end end

1. The standard ||= approach

obj = MyClass.new obj.val ||= 'a' obj.val ||= 'b'

2. The a = a || b approach

obj = MyClass.new obj.val = obj.val || 'c' obj.val = obj.val || 'd'

3. The a || a = b approach

obj = MyClass.new obj.val || obj.val = 'e' obj.val || obj.val = 'f'

And the output shows off similar behavior to the hash and array example:

Setting val to "a" Setting val to "c" Setting val to "c" Setting val to "e"

Default Hash Values: A Sneaky Edge Case?

Our travels don't end there though. Back in 2008, David Black noticed an edge case with hashes that have default values. If you follow the logic above to the letter, this case will not surprise you, although from a pragmatic point of view, it's curious.

Let's take a look:

hsh = Hash.new('default')

hsh[:x] # => 'default'

1. The standard ||= approach

hsh[:x] ||= 10 p hsh # => {}

2. The a = a || b approach

hsh[:y] = hsh[:y] || 10 p hsh # {:y=>"default"}

3. The a || a = b approach

hsh[:z] || hsh[:z] = 10 p hsh # {:y=>"default"}

Hashes with default values act in an.. interesting way, depending on your point of view. Merely accessing a value doesn't mean that the value is reified (made concrete) in the hash itself. The reason for this is that you can assign Procs to a hash's default_proc in order to perform calculations (or even to set values) when an unset key is accessed. It would be undesirable to avoid this behavior merely because a key was accessed earlier on.

Again, we note that the a || a = b-style approach gives the result closest to the reality of ||=.

describe "Conditional operator assignment 'obj.meth op= expr'" do
it "is equivalent to 'obj.meth op obj.meth = expr'" do

RubySpec's variables_spec file

Undefined Variables: Another Tricky Case

In the comments for this post, Vikrant Chaudhary brought up another interesting case:

If a is not defined, a || a = 42 raises NameError, while a ||= 42 returns 42. So, they don't seem to be equivalent expressions.

Vikrant Chaudhary

It's lucky I said "behaves like" earlier - phew! But joking aside, Vikrant makes a good point.

This tricky case is a little like the hash case. Something intriguing about how Ruby operates behind the scenes throws a spanner into the works again. That is, a variable assignment, even if not run, immediately summons that variable into being. For example:

x = 10 if 2 == 5 puts x

Even though the first line won't be run, x will exist on the second line and no exception will be raised. Another nasty one:

x = x puts x

Whoa! Well, a ||= 42 is working in a similar way. Ruby sees the assignment at the parsing stage and creates the variable in a way that it wouldn't with a || a = 42, even though it ends up behaving like the latter once actual execution occurs.

Further Reading

This appears to have been a popular discussion point in Rubyland over the years, so I would be remiss not to include links to some of the best references: