A colleague recently brought up the fact that is is possible to do this:

class Symbol
  def +(other_thing)
    "#{self} plus #{other_thing}"
  end
end

:something + "something else"
#=> "something plus something else"

But not this:

class Symbol
  def ||(other_thing)
    "#{self} or #{other_thing}"
  end
end

:something || "something else"

# 2: syntax error, unexpected ||
#   def ||(other_thing)
#         ^
# 5: syntax error, unexpected keyword_end, expecting end-of-input

Adding a + method works fine, but adding a || method causes a syntax error. Let’s look at some of Ruby’s virtual machine instructions to find out what is going on here.

puts RubyVM::InstructionSequence.compile(':a + :b').disasm

# 0002 putobject         :a
# 0004 putobject         :b
# 0006 opt_plus         <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
# 0009 leave 

This is not surprising. Ruby puts two objects (putobject at 0002 and 0004) onto its internal stack and then calls the + method (at 0006). (It doesn’t matter at compile time that symbols wouldn’t normally have a + method. The method might get defined later, or else it will raise a NoMethodError at runtime.)

Compare that to the instructions for ||:

puts RubyVM::InstructionSequence.compile(':a || :b').disasm

# 0002 putobject        :a
# 0004 dup              
# 0005 branchif         10
# 0007 pop              
# 0008 putobject        :b
# 0010 leave 

This is quite different! The instructions for || do not involve a Ruby method call at all. Instead, Ruby puts an object on the stack (at 0002), then uses branchif (at 0005) to either jump to instruction 0010 or pop the first object off the stack (at 0007) and replace it with a new object (at 0008).

&& works quite the same way, but with branchunless:

puts RubyVM::InstructionSequence.compile(':a && :b').disasm

# 0002 putobject        :a
# 0004 dup              
# 0005 branchunless     10
# 0007 pop              
# 0008 putobject        :b
# 0010 leave 

The instructions for an if/else statement are quite similar:

code = <<-RUBY
  if :a
    :a
  else
    :b
  end
RUBY

puts RubyVM::InstructionSequence.compile(code).disasm

# 0002 putobject        :a
# 0004 branchunless     9
# 0006 putobject        :a
# 0008 leave            
# 0009 putobject        :b
# 0011 leave  

There is some difference since we are not using the condition directly as a return value, but I think the similarities are clear. && and || are used for control flow much like if and unless. They behave more like keywords than method calls.

Just to confirm || is really not a method:

[].method(:+)
#=> #<Method: Array#+>

[].method(:cheese)
#=> NameError: undefined method `cheese' for class `Array'

[].method(:||)
# SyntaxError: unexpected ||, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
# [].method(:||)
#              ^

Yeah, definitely not a method.

If you have been following along in pry, you will find that I lied about most of the virtual machine instructions above. Ruby actually does some optimization at compile time to get rid of instructions that will never get executed. Since symbols are always truthy, the examples above actual compile as follows:

puts RubyVM::InstructionSequence.compile(':a || :b').disasm

# 0002 putobject        :a
# 0004 leave
puts RubyVM::InstructionSequence.compile(':a && :b').disasm

# 0002 putobject        :b
# 0004 leave
code = <<-RUBY
  if :a
    :a
  else
    :b
  end
RUBY

puts RubyVM::InstructionSequence.compile(code).disasm

# 0002 putobject        :a
# 0004 leave 

Thanks, Ruby!

There is still room for improvement though. This gets optimized:

code = <<-RUBY
  if [1]
    [1]
  else
    [2]
  end
RUBY

puts RubyVM::InstructionSequence.compile(code).disasm

# 0002 duparray         [1]
# 0004 leave 

But for some reason this doesn’t:

puts RubyVM::InstructionSequence.compile('[1] || [2]').disasm

# 0002 duparray         [1]
# 0004 dup              
# 0005 branchif         10
# 0007 pop              
# 0008 duparray         [2]
# 0010 leave

If you like this sort of thing, you should check out Ruby Under a Microscope