Why can Ruby Procs sometimes return and sometimes it is an error?

Viewed 57

Two very similar examples in this article:

def proc_return
  Proc.new { return "Proc.new"}.call
  return "proc_return method finished"
end

def lambda_return
  lambda { return "lambda" }.call
  return "lambda_return method finished"
end

puts proc_return
puts lambda_return

# => Proc.new
# => lambda_return method finished

and

def generic_return(code)
  code.call
  return "generic_return method finished"
end

puts generic_return(Proc.new { return "Proc.new" })
puts generic_return(lambda { return "lambda" })

# => *.rb:6: unexpected return (LocalJumpError)
# => generic_return method finished

Why is this permitted in proc_return

Proc.new { return "Proc.new"}.call

but (something equivalent to) this is not premitted in generic_return

code = Proc.new { return "Proc.new" }
code.call

?

2 Answers

Because proc as lambda is a closure. Closures remember the environment when they were created. One of the differences between proc as lambda that if you call return inside proc it will return from the block (or blocks) of code when it was created. lambda returns only from its own block of code.

Here the environment is a method proc_return

def proc_return
  Proc.new { return "Proc.new"}.call
  return "proc_return method finished"
end

While here the environment is a global namespace:

def generic_return(code)
  code.call
  return "generic_return method finished"
end

puts generic_return(Proc.new { return "Proc.new" })

Let's make it obvious:

proc_object = Proc.new { return "Proc.new" }

puts generic_return(proc_object)

proc_object remember that it was created in a global environment when you call it inside generic_return environment, it tries to exit the global one.

You can get the same error just typing return in irb console:

$ irb
2.5.1 :001 > return
Traceback (most recent call last):
        2: from /home/user/.rvm/rubies/ruby-2.5.1/bin/irb:11:in `<main>'
        1: from (irb):1
LocalJumpError (unexpected return)
2.5.1 :002 >

There is a good mnemonic that I use:

  • "Proc" rhymes with "block", so Proc behaves like a block.
  • "lambda" and "method" are both Greek, so lambda behaves like a method.

return in a method returns from the method itself, therefore, return in a lambda returns from the lambda itself.

return in a block returns from the closest lexically enclosing method, therefore return in a Proc returns from the closest lexically enclosing method.

In your second example, there is no lexically enclosing method to return from, therefore you get a LocalJumpError.

By the way, the other aspect where lambdas behave like methods and Procs behave like blocks is that lambdas have strict argument binding (like methods) and Procs have loose argument binding (like blocks).

Related