Read more

Ruby: A small summary of what return, break and next means for blocks

Emanuel
August 23, 2017Software engineer at makandra GmbH

Summary

  • Use return to return from a method. return accepts a value that will be the return value of the method call.
  • Use break to quit from a block and from the method that yielded to the block. break accepts a value that supplies the result of the expression it is “breaking” out of.
  • Use next to skip the rest of the current iteration. next accepts an argument that will be the result of that block iteration.

The following method will serve as an example in the details below:

def example
  puts yield
  puts 'done'
  return 'example'
end

# Intended usage & output:
example { 'hallo welt' }
# hallo welt
# done
# => 'example'

Return within a block

Illustration book lover

Growing Rails Applications in Practice

Check out our e-book. Learn to structure large Ruby on Rails codebases with the tools you already know and love.

  • Introduce design conventions for controllers and user-facing models
  • Create a system for growth
  • Build applications to last
Read more Show archive.org snapshot

Return is only valid inside a method. If you use it inside a block or not is not relevant. Return lets you jump out of a method and returns nil or an argument.

return # => LocalJumpError: unexpected return
example { return 4 } # => LocalJumpError: unexpected return

def test
  example { return 4 }
  return 'test'
end

test # => 4

Note how test returns the return value from the block; neither code after the example invocation (returning "test") nor code after the yield inside example (putsing "done", returning "example") are executed. As you always knew, and in blocks too: return exits that method NOW.

If you nest blocks return is still jumping out of the method (and not out of the first block or something similar).

def test
  [1, 2, 3].each do |counter|
    example { return counter }
  end
  return 'test'
end

test # => 1

Conclusion: return has no special meaning in blocks, but it can be misunderstood as "return from block", which is wrong. See Fun with Ruby: Returning in blocks "overwrites" outside return values for an example.

Break within a block

Break is only valid within a block. It lets you jump out of a block and returns nil or the provided argument to the caller.

break # => SyntaxError: Can't escape from eval with break

example { break 'break' }
# => 'break'

Note how break changes the return value of the method yielding to the block from example to its argument. Also, the code after the yield in example is not executed! Probably, this behavior was designed to enable programmers writing their own iterators (like while or loop) as methods and still get all the keyword love from Ruby.

As a side note, using break also could indicate a code smell (when we look at what was said above about the expected return value):

# bad
[1, 2, 3].each { |counter| break counter if counter.even? } # => 2
[1, 3].each { |counter| break counter if counter.even? } # => [1, 3]

# good
[1, 2, 3].find(&:even?) # => 2
[1, 3].find(&:even?) # => nil

Next within a block

Use next to skip the rest of the current iteration. next accepts an argument that can be used as the result of the current block iteration.

next # => SyntaxError: (irb):24: Can't escape from eval with next

example { next 'next' }
'next'
'done'
# => 'example'

Note how next exits the block returning its argument as block return value, but the example method still gets to continue with its code after the yield.

A real world example could be logging on user creation without changing the return value:

def save_user
  user.save.tap do |saved|
    next unless saved
    Rails.log("User was created, we have #{user.count} users now!")  
  end
end

save_user
# User was created, we have #{user.count} users now!
# => true

Other rules apply to lambdas

A lambda (lambda { ... } or -> { ... }) cannot return from the containing function.

Emanuel
August 23, 2017Software engineer at makandra GmbH
Posted by Emanuel to makandra dev (2017-08-23 08:52)