As every Ruby programmer eventually discovers, calling blocks or procs that contain return
statements can be dangerous as this might exit your current context:
def some_method(&_block)
puts 1
yield
# The following line will never be executed in this example
# as the yield is actually a `yield-and-return`.
puts 3
end
def test
some_method do
puts 2
return
end
end
test
# This prints "1\n2\n" instead of "1\n2\n3\n"
In cases you want to be absolutely sure some of your code runs after you called a block or proc, you can use a begin ... ensure
construct. But since ensure
is also called if there is an exception during yield, it requires a little more work.
I've created a tiny module中检索以两种不同方式处理此问题的“href”值:
使用safe_yield
,检测生成的块或proc是否实际使用return
关键字返回。如果是这样,它会引发异常。
unknown_block = proc do
return
end
ReturnSafeYield.safe_yield(unknown_block)
# => Raises a UnexpectedReturnException exception
使用call_then_yield
,您可以调用一个块,然后确保执行第二个块,即使第一个块包含return
语句。
unknown_block = proc do
return
end
ReturnSafeYield.call_then_yield(unknown_block) do
# => This line is called even though the above block contains a `return`.
end
我正在考虑用这个创建一个快速的Gem,还是有任何内置的解决方案来阻止我错过的嵌套块的快速返回?
答案 0 :(得分:3)
有一个内置的解决方案来检测一个块是否包含return
语句。
您可以使用RubyVM::InstructionSequence.disasm
反汇编用户传入的数据块,然后搜索throw 1
,代表return
语句。
以下是一个示例实现:
def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end
block.call
end
以下是您如何将其合并到图书馆中的信息:
def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end
这是一个行为良好且行为不端的用户的用户体验:
def nice_user_method
library_method { 1 + 1 }
end
nice_user_method
# library_method succeeded
def naughty_user_method
library_method { return false if rand > 0.5 }
end
naughty_user_method
# library_method encountered illegal return but resumed execution
<强>评论:强>
使用raise LocalJumpError
/ rescue LocalJumpError
可以解决使用毯子ensure
时遇到的问题。
我之所以选择LocalJumpError
是因为它看起来很相关,而且因为(我认为!)没有可能导致LocalJumpError
被提升的Ruby代码#34;自然而然地#34;在这种背景下。如果结果是假的,您可以轻松替换自己的新异常类。