59

In Perl, there is an ability to break an outer cycle like this:

AAA: for my $stuff (@otherstuff) {
         for my $foo (@bar) {
             last AAA if (somethingbad());
         }
      }

(syntax may be wrong), which uses a loop label to break the outer loop from inside the inner loop. Is there anything similar in Ruby?

ire_and_curses
  • 68,372
  • 23
  • 116
  • 141
Fluffy
  • 27,504
  • 41
  • 151
  • 234

8 Answers8

111

Consider throw/catch. Normally the outside loop in the below code will run five times, but with throw you can change it to whatever you like, breaking it in the process. Consider this perfectly valid ruby code:

catch (:done) do
  5.times { |i|
    5.times { |j|
      puts "#{i} #{j}"
      throw :done if i + j > 5
    }
  }
end
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Chris Bunch
  • 87,773
  • 37
  • 126
  • 127
  • 5
    Personally I don't like using exception raising for normal code execution. It forces a programmer the follow multiple flows of logic. – Jeff Waltzer Aug 30 '09 at 16:24
  • 19
    I don't understand this comment. In the code snippet above there are no exceptions raised anywhere. There are only six messages sent in the entire piece of code: `catch`, `times`, `puts`, `throw`, `+` and `<=>`. I don't send of `raise` anywhere. – Jörg W Mittag Aug 31 '09 at 09:33
  • 21
    I guess there is a mix up between raise/rescue (exceptions) and throw/catch, they looks similar but are not. – Kris Jan 26 '11 at 16:49
  • 9
    "While the exception mechanism of raise and rescue is great for abandoning execution when things go wrong, it's sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy." (http://ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html) – Ryenski Aug 02 '11 at 14:57
  • 1
    Here's an example of using throw to get out of a long loop when a condition is met: https://gist.github.com/1120376 – Ryenski Aug 02 '11 at 15:08
  • The throw and catch is a perfect analogy...This is beautiful – Felipe Mar 07 '12 at 23:59
39

What you want is non-local control-flow, which Ruby has several options for doing:

  • Continuations,
  • Exceptions, and
  • throw/catch

Continuations

Pros:

  • Continuations are the standard mechanism for non-local control-flow. In fact, you can build any non-local control-flow (subroutines, procedures, functions, methods, coroutines, state machines, generators, conditions, exceptions) on top of them: they are pretty much the nicer twin of GOTO.

Cons:

  • Continuations are not a mandatory part of the Ruby Language Specification, which means that some implementations (XRuby, JRuby, Ruby.NET, IronRuby) don't implement them. So, you can't rely on them.

Exceptions

Pros:

  • There is a paper that proves mathematically that Exceptions can be more powerful than Continuations. IOW: they can do everything that continuations can do, and more, so you can use them as a replacement for continuations.
  • Exceptions are universally available.

Cons:

  • They are called "exceptions" which makes people think that they are "only for exceptional circumstances". This means three things: somebody reading your code might not understand it, the implementation might not be optimized for it (and, yes, exceptions are godawful slow in almost any Ruby implementation) and worst of all, you will get sick of all those people constantly, mindlessly babbling "exceptions are only for exceptional circumstances", as soon as they glance at your code. (Of course, they won't even try to understand what you are doing.)

throw/catch

This is (roughly) what it would look like:

catch :aaa do
  stuff.each do |otherstuff|
    foo.each do |bar|
      throw :aaa if somethingbad
    end
  end
end

Pros:

  • The same as exceptions.
  • In Ruby 1.9, using exceptions for control-flow is actually part of the language specification! Loops, enumerators, iterators and such all use a StopIteration exception for termination.

Cons:

  • The Ruby community hates them even more than using exceptions for control-flow.
sawa
  • 165,429
  • 45
  • 277
  • 381
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • For the benefit of 2011 readers: http://www.coffeepowered.net/2011/06/17/jruby-performance-exceptions-are-not-flow-control/ indicates that JRuby is still somewhat slow at Exceptions. – Andrew Grimm Jun 24 '11 at 04:43
  • 2
    I've never heard of the ruby community hating `throw`/`catch` (or even exceptions) for control flow. Do they prefer continuations? Did you read about this sentiment on ruby-talk or something? – Kelvin Apr 04 '13 at 16:34
32

No, there isn't.

Your options are:

  • put the loop in a method and use return to break from the outer loop
  • set or return a flag from the inner loop and then check that flag in the outer loop and break from it when the flag is set (which is kind of cumbersome)
  • use throw/catch to break out of the loop
sepp2k
  • 363,768
  • 54
  • 674
  • 675
3
while c1
 while c2
    do_break=true
 end
 next if do_break
end

or "break if do_break" depending on what you want

Anno2001
  • 1,343
  • 12
  • 17
2

Perhaps this is what you want? (not tested)

stuff.find do |otherstuff|
  foo.find do
    somethingbad() && AAA
  end
end

The find method keeps looping until the block returns a non null value or the end of the list is hit.

Jeff Waltzer
  • 532
  • 5
  • 12
1

Wrapping an internal method around the loops could do the trick Example:

test = [1,2,3]
test.each do |num|
  def internalHelper
    for i in 0..3 
      for j in 0..3
        puts "this should happen only 3 times"
        if true
          return
        end
      end
    end
  end
internalHelper
end

Here you can do a check inside any of the for loops and return from the internal method once a condition is met.

Metareven
  • 822
  • 2
  • 7
  • 26
0

I know I will regret this in the morning but simply using a while loop could do the trick.

x=0
until x==10
  x+=1
  y=0
  until y==10
    y+=1
    if y==5 && x==3
      x,y=10,10
    end
  end
  break if x==10
  puts x
end

The if y==5 && x==3 is only an example of an expression turning true.

Jonas Elfström
  • 30,834
  • 6
  • 70
  • 106
0

You may consider adding a flag, which is set inside the inner loop, for controlling the outer loop.

'next' the outer loop

for i in (1 .. 5)
  next_outer_loop = false
  for j in (1 .. 5)
    if j > i     
      next_outer_loop = true if j % 2 == 0
      break      
    end          
    puts "i: #{i}, j: #{j}"
  end            
  print "i: #{i} "                                                                                                                                                                             
  if next_outer_loop
    puts "with 'next'"
    next         
  end            
  puts "withOUT 'next'"
end

'break' the outer loop

for i in (1 .. 5)
  break_outer_loop = false
  for j in (1 .. 5)
    if j > i
      break_outer_loop = true if i > 3
      break
    end
    puts "i: #{i}, j: #{j}"
  end
  break if break_outer_loop
  puts "i: #{i}"
end
Stephen LAI
  • 220
  • 3
  • 5