Guard clauses and modifiers

return unless

A guard clause handles the boring case at the top of a method and leaves the rest of the body for the real work. Ruby reads it as one line, and the reader stops worrying about the edge case after the first line:

def double(x) return :none if x.nil? x * 2 end puts double(2)

ruby -e 'def f(x) = (return :none if x.nil?; x * 2); puts f(2)'

4

unless is the negative form, and return can carry a value: return [] unless File.exist?(path) is the same guard with the empty answer in it.

next if in a loop

The same idea works inside a loop. next skips the rest of the block for one item, and break leaves the loop:

ruby -e '[1, 2, 3, 4].each { |n| next if n.even?; print n }; puts'

13

The modifier form is the idiom: a condition at the end of a one-line body reads as "do this, except when". A block with two guards at the top is usually a filter that should have been select.

A method rewritten with guards

The nested version is what a guard clause replaces. Both methods answer the same thing; the second one has no indentation to follow:

def check(x) if x.nil? :none else if x < 0 :neg else x end end end

def check(x) return :none if x.nil? return :neg if x < 0 x end

ruby -e 'def check(x); return :none if x.nil?; return :neg if x < 0; x; end; puts check(-1); puts check(2)'

neg
2

Two guards and one return line is about the limit. After three, the conditions are the method, and a small lookup or a case says it better.

What to watch for

A modifier changes the flow quietly: puts x if x hides a branch where a reader expects a statement. Keep the modifier for one condition and no else; write the full if when the body has more than one line.

In a method that returns a value, the guard returns early and the body returns last. A modifier on the last expression (x * 2 if x) returns nil when the condition is false, which is easy to miss.

Next: break, next, redo and retry

Article author: Arthur Isaev