You want to interrupt an iteration from within the code block you passed into it.
The simplest way to interrupt execution is to use break. Abreakstatement will jump out of the closest enclosing loop defined in the current method:
1.upto(10) do |x| puts x break if x == 3 end # 1 # 2 # 3
The break statement is simple but it has several limitations. You can’t use breakwithin a code block defined withProc.newor (in Ruby 1.9 and up)Kernel#proc. If this is a problem for you, uselambdainstead:
aBlock = Proc.new do |x| puts x break if x == 3 puts x +2 end
aBlock.call(5) # 5 # 7
aBlock.call(3) # 3 # LocalJumpError: break from proc-closure
More seriously, you can’t usebreakto jump out of multiple loops at once. Once a loop has run, there’s no way to know whether it completed normally or by usingbreak.
The simplest way around this problem is to enclose the code you want to skip within acatchblock with a descriptive symbolic name. You can thenthrowthe corresponding symbol when you want to jump to the end of thecatchblock. This lets you skip out of any number of nested loops and method calls.
Thethrow/catchsyntax isn’t exception handling—exceptions use araise/rescuesyntax. This is a special flow control construct designed to replace the use of exceptions for flow control (as sometimes happens in Java programs). It’s a bit like an old-style global GOTO, capable of suddenly moving execution to a faraway part of your program. It keeps your code more readable than a GOTO, though, because it’s restricted: athrowcan only jump to the end of a correspondingcatchblock.
The best example of thecatch..throwsyntax is theFind.findfunction described in Recipe 6.12. When you pass a code block intoFind.find, it yields up every directory and file in a certain directory tree. When your code block is given a directory, it can stopfindfrom recursing into that directory by callingFind.prune, which throws a:prunesymbol. Usingbreakwould stop thefindoperation altogether; throwing a symbol letsFind.pruneknow to just skip one directory.
Here’s a simplified view of theFind.findandFind.prunecode:
def find(*paths) paths.each do |p| catch(:prune) do # Process p as a file or directory... end # When you call Find.prune you'll end up here. end end
def prune throw :prune end
When you callFind.prune, execution jumps to immediately after thecatch(:prune)block.Find.findthen starts processing the next file or directory.