Many languages support breaking out of nested loops. There are some typical ways of doing this:

  • Some languages can name loops by providing a label for the loop. In those languages, you can use break together with a label to specify which loop to break out of. Examples: Perl, Java, JavaScript, and some others.
  • Some languages can specify the number of layers of loops to break out of. In those languages, you can use break together with a number to specify how many layers of loops to break out of. The only example that I know is C#.
  • Some languages have goto statements. You can easily break from loops to wherever you want by using goto (actually breaking out of nested loops is among the only recommended cases for using goto). Examples: C, C++.

However, in most other languages, it is not easy to break out of nested loops. A typical solution is this:

1
2
3
4
5
6
7
8
9
10
outer_loop do
	break_outer = false
	inner_loop do
		if condition
			break_outer = true
			break
		end
	end
	break if break_outer
end

In languages with exceptions, another possible workaround is to use exceptions (the catch–throw control flow):

1
2
3
4
5
6
7
catch :outer_loop do
	outer_loop do
		inner_loop do
			throw :outer_loop if condition
		end
	end
end

I wrote a simple module to better use this workaround.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class JumpLabel < StandardError
	attr_reader :reason, :arg
	{break: true, next: true, redo: false}.each do |reason, has_args|
		define_method reason do |*args|
			@reason = reason
			@arg = args.size == 1 ? args.first : args if has_args
			raise self
		end
	end
end

class Module
	def register_label *method_names
		method_names.each do |name|
			old = instance_method name
			define_method name do |*args, **opts, &block|
				return old.bind_call self, *args, **opts unless block
				old.bind_call self, *args, **opts do |*bargs, **bopts, &bblock|
					block.call *bargs, **bopts, jump_label: label = JumpLabel.new, &bblock
				rescue JumpLabel => catched_label
					raise catched_label unless catched_label == label
					case label.reason
					when :break then break label.arg
					when :next then next label.arg
					when :redo then redo
					end
				end
			end
		end
	end
end

Example usage:

1
2
3
4
5
6
7
8
9
Integer.register_label :upto, :downto
1.upto 520 do |i, jump_label:|
	print i
	1.downto -1314 do |j|
		print j
		jump_label.break 8 if j == 0
	end
end.tap { puts _1 }
# => 1108