Search code examples
rubyreturnprogram-entry-point

`return` in main environment


When I run the following in the main environment in irb or pry,

return "foo"

I get:

LocalJumpError: unexpected return

which is expected. But when I write this code in a separate file foo.rb, and do:

$ ruby foo.rb

in the console, it terminates without any error message.

What is happening in the latter case?

I thought a raised error might be silently disposed with, and tried the following in a separate file:

begin
  return "foo"
rescue Exception => e
  p e
end

and executed it from the console, but this also resulted in no error.

When I put the code in an environment such as a module, it returns the error:

module A
  return "foo" # >> Invalid return in class/module body
end

Solution

  • Top-level return was added as a feature in ruby 2.4.0: https://bugs.ruby-lang.org/issues/4840

    It was never mentioned in the release announcement, but you can see it listed under the "language changes" in the more detailed news post.

    If you run a file which contains return "foo" in ruby version < 2.4.0, you will indeed see an error like:

    foo.rb:1:in `<main>': unexpected return (LocalJumpError)
    

    The intended use case (as you can see from the above link) was for "cancelling a require" - e.g. if the file is platform-specific, such as:

    require "test/unit"
    
    return unless /mswin|cygwin|mingw|bccwin/
    
    # ...
    

    There was much discussion and debate over proposed syntax and behaviour; but the chosen implementation can perhaps be best understood by reading the implementation's test cases:

    def test_return_toplevel
      feature4840 = '[ruby-core:36785] [Feature #4840]'
      code = <<~'CODE'
      return; raise
      begin return; rescue SystemExit; exit false; end
      begin return; ensure exit false; end
      begin ensure return; end
      begin raise; ensure; return; end
      begin raise; rescue; return; end
      return false; raise
      return 1; raise
      CODE
      all_assertions(feature4840) do |a|
        code.each_line do |s|
          s.chomp!
          a.for(s) {assert_ruby_status([], s)}
        end
      end
    end
    

    Since writing return inside a REPL like pry is not at the "top level", this results in a LocalJumpError instead of the above special behaviour for "top-level return".