How to break out from a ruby block?
Asked Answered
C

8

463

Here is Bar#do_things:

class Bar   
  def do_things
    Foo.some_method(x) do |x|
      y = x.do_something
      return y_is_bad if y.bad? # how do i tell it to stop and return do_things? 
      y.do_something_else
    end
    keep_doing_more_things
  end
end

And here is Foo#some_method:

class Foo
  def self.some_method(targets, &block)
    targets.each do |target|
      begin
        r = yield(target)
      rescue 
        failed << target
      end
    end
  end
end

I thought about using raise, but I am trying to make it generic, so I don't want to put anything any specific in Foo.

County answered 10/9, 2009 at 0:0 Comment(0)
L
805

Use the keyword next. If you do not want to continue to the next item, use break.

When next is used within a block, it causes the block to exit immediately, returning control to the iterator method, which may then begin a new iteration by invoking the block again:

f.each do |line|              # Iterate over the lines in file f
  next if line[0,1] == "#"    # If this line is a comment, go to the next
  puts eval(line)
end

When used in a block, break transfers control out of the block, out of the iterator that invoked the block, and to the first expression following the invocation of the iterator:

f.each do |line|             # Iterate over the lines in file f
  break if line == "quit\n"  # If this break statement is executed...
  puts eval(line)
end
puts "Good bye"              # ...then control is transferred here

And finally, the usage of return in a block:

return always causes the enclosing method to return, regardless of how deeply nested within blocks it is (except in the case of lambdas):

def find(array, target)
  array.each_with_index do |element,index|
    return index if (element == target)  # return from find
  end
  nil  # If we didn't find the element, return nil
end
Letha answered 10/9, 2009 at 0:2 Comment(7)
thanks, but next only moves to the next item in the array. is it possible to exit?County
You have to call next with the return value. "def f; x = yield; puts x; end" "f do next 3; puts "o"; end" This prints 3 (but no "o") on the console.Disloyal
next, break, return, you can't compareKrypton
I added an answer expanding on the comment @MarcelJackwerth added about using next or break with an argument.Reremouse
There's also a keyword called redo, which basically just moves execution back to the top of the block within the current iteration.Unpen
A little side question: would it still be ok to use break in a File block like this: File.foreach(filename) { break if condition } ? Would that leave the file opened?Clairvoyant
hi, returning control to the iterator method, if the method that execute the block doesn't have iterator outside, how next, break behave? Are they the same?Hibernal
G
70

I wanted to just be able to break out of a block - sort of like a forward goto, not really related to a loop. In fact, I want to break of of a block that is in a loop without terminating the loop. To do that, I made the block a one-iteration loop:

for b in 1..2 do
    puts b
    begin
        puts 'want this to run'
        break
        puts 'but not this'
    end while false
    puts 'also want this to run'
end

Hope this helps the next googler that lands here based on the subject line.

Gynarchy answered 12/4, 2012 at 4:14 Comment(2)
This was the only response that answered the question as it was put. Deserves more points. Thanks.Aestivate
This works the same for both break and next. If the false is changed to true then next will stay in the look and break will break out.Headon
R
42

If you want your block to return a useful value (e.g. when using #map, #inject, etc.), next and break also accept an argument.

Consider the following:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    if x % 3 == 0
      count + 2
    elsif x.odd?
      count + 1
    else 
      count
    end
  end
end

The equivalent using next:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    next count if x.even?
    next (count + 2) if x % 3 == 0
    count + 1
  end
end

Of course, you could always extract the logic needed into a method and call that from inside your block:

def contrived_example(numbers)
  numbers.inject(0) { |count, x| count + extracted_logic(x) }
end

def extracted_logic(x)
  return 0 if x.even?
  return 2 if x % 3 == 0
  1
end
Reremouse answered 26/7, 2012 at 21:33 Comment(2)
could you please update w/ a specific example using break (probably just replace one of your next with break ..Sitton
One very interesting thing. break something works, break(something) works but true && break(somehting) yields a syntax error. Just FYI. If condition is needed, then if or unless needs to be used.Deal
H
23

use the keyword break instead of return

Hangeron answered 10/9, 2009 at 0:52 Comment(0)
N
8

Perhaps you can use the built-in methods for finding particular items in an Array, instead of each-ing targets and doing everything by hand. A few examples:

class Array
  def first_frog
    detect {|i| i =~ /frog/ }
  end

  def last_frog
    select {|i| i =~ /frog/ }.last
  end
end

p ["dog", "cat", "godzilla", "dogfrog", "woot", "catfrog"].first_frog
# => "dogfrog"
p ["hats", "coats"].first_frog
# => nil
p ["houses", "frogcars", "bottles", "superfrogs"].last_frog
# => "superfrogs"

One example would be doing something like this:

class Bar
  def do_things
    Foo.some_method(x) do |i|
      # only valid `targets` here, yay.
    end
  end
end

class Foo
  def self.failed
    @failed ||= []
  end

  def self.some_method(targets, &block)
    targets.reject {|t| t.do_something.bad? }.each(&block)
  end
end
Nanna answered 10/9, 2009 at 7:15 Comment(3)
Don't add arbitrary methods like that to the Array class! That's really bad practice.Threat
Rails does it, so why can't he?Katsuyama
@Katsuyama That's not to say that it necessarily should. ;) In general, though, it's best to avoid monkey patching core classes unless you have a pretty good reason (i.e. adding some very useful generalizable functionality that lots of other code will find useful). Even then, tread lightly because once a class is heavily monkey-patched, it's easy for libraries to start walking over each other and causing some extremely odd behavior.Nubbly
H
2

next and break seem to do the correct thing in this simplified example!

class Bar
  def self.do_things
      Foo.some_method(1..10) do |x|
            next if x == 2
            break if x == 9
            print "#{x} "
      end
  end
end

class Foo
    def self.some_method(targets, &block)
      targets.each do |target|
        begin
          r = yield(target)
        rescue  => x
          puts "rescue #{x}"
        end
     end
   end
end

Bar.do_things

output: 1 3 4 5 6 7 8

Headon answered 28/5, 2014 at 22:2 Comment(1)
break ends immediately - next continues to the next iteration.Krispin
C
2

You have four ways to unwind the stack in 'non-exceptional' ways: next, break, return, and throw.

next will cause the block to return.

break will cause the method that yielded to the block to return.

return will cause the method where the block is defined to return.

throw will traverse up the stack until it finds a catch with a matching symbol, and cause that to return. This is much like a 'lightweight' exception for non-exceptional situations.

All of them can take a return value that will be returned by whatever they caused to return instead of the value their last expression which they would return normally.

Here are some examples:

def doSomething
  puts "> doSomething"
  yield
  puts "< doSomething"
end

def withNext
  puts "> withNext"
  doSomething do
    puts "> block"
    puts "* NEXT! causes the block to return to doSomething"
    next
    puts "< block"
  end
  puts "< withNext"
end

def withBreak
  puts "> withBreak"
  doSomething do
    puts "> block"
    puts "* BREAK! causes doSomething to return to withBreak"
    break
    puts "< block"
  end
  puts "< withBreak"
end

def withReturn
  puts "> withReturn"
  doSomething do
    puts "> block"
    puts "* RETURN! causes withReturn to return"
    return
    puts "< block"
  end
  puts "< withReturn"
end

def withThrow
  puts "> withThrow"
  catch :label do
    puts "> catch :label"
    doSomething do
      puts "> block 1"
      doSomething do
        puts "> block 2"
        puts "* THROW! causes catch :label to return to withThrow"
        throw :label
        puts "< block 2"
      end
      puts "< block 1"
    end
    puts "< catch :label"
  end
  puts "< withThrow"
end

withNext
puts "* Done"
puts
withBreak
puts "* Done"
puts
withReturn
puts "* Done"
puts
withThrow
puts "* Done"

output:

> withNext
> doSomething
> block
* NEXT! causes the block to return to doSomething
< doSomething
< withNext
* Done

> withBreak
> doSomething
> block
* BREAK! causes doSomething to return to withBreak
< withBreak
* Done

> withReturn
> doSomething
> block
* RETURN! causes withReturn to return
* Done

> withThrow
> catch :label
> doSomething
> block 1
> doSomething
> block 2
* THROW! causes catch :label to return to withThrow
< withThrow
* Done
Corpulent answered 11/2, 2022 at 3:39 Comment(0)
E
-4

To break out from a ruby block simply use return keyword return if value.nil? next.

  • next terminates lambda, block, or proc it is in.
  • break terminates the method that yielded to the block or invoked the proc or lambda it is in.

Credit to: Ruby block return, break, next

Ectomorph answered 13/6, 2017 at 14:45 Comment(3)
Doesn't return exit the function?Ultramicrochemistry
shouldn't use return to exit a block in ruby !Companionable
I stand corrected.Ectomorph

© 2022 - 2024 — McMap. All rights reserved.