Stopping an Iteration

Problem

You want to interrupt an iteration from within the code block you passed into it.

Solution

The simplest way to interrupt execution is to use break. A break statement 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

 

Discussion

The break statement is simple but it has several limitations. You can't use break within a code block defined with Proc.new or (in Ruby 1.9 and up) Kernel#proc. If this is a problem for you, use lambda instead:

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 use break to jump out of multiple loops at once. Once a loop has run, there's no way to know whether it completed normally or by using break.

The simplest way around this problem is to enclose the code you want to skip within a catch block with a descriptive symbolic name. You can then throw the corresponding symbol when you want to jump to the end of the catch block. This lets you skip out of any number of nested loops and method calls.

The tHRow/catch syntax isn't exception handlingexceptions use a raise/rescue syntax. 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: a tHRow can only jump to the end of a corresponding catch block.

The best example of the catch..throw syntax is the Find.find function described in Recipe 6.12. When you pass a code block into Find.find, it yields up every directory and file in a certain directory tree. When your code block is given a directory, it can stop find from recursing into that directory by calling Find.prune, which throws a :prune symbol. Using break would stop the find operation altogether; throwing a symbol lets Find.prune know to just skip one directory.

Here's a simplified view of the Find.find and Find.prune code:

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 call Find.prune, execution jumps to immediately after the catch(:prune) block. Find.find then starts processing the next file or directory.

See Also

Категории