使用 Ruby `yield` 时如何防止 `return` 出现问题
How to prevent problems with `return` from block when using Ruby `yield`
正如每个 Ruby 程序员最终发现的那样,调用包含 return
语句的块或过程可能很危险,因为这可能会退出您当前的上下文:
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"
如果您想绝对确定某些代码在 调用块或过程后 运行,您可以使用 begin ... ensure
结构。但是由于如果在 yield 期间出现异常也会调用 ensure
,因此需要多做一些工作。
我创建了一个 tiny module 以两种不同的方式处理这个问题:
使用safe_yield
,使用return
关键字检测产生的块或proc实际上是否returns。如果是这样,它会引发异常。
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
我正在考虑从中创建一个 quick Gem,或者是否有任何内置解决方案来防止我错过的嵌套块中的 quick return?
有一个内置的解决方案可以检测一个块是否包含 return
语句。
可以用RubyVM::InstructionSequence.disasm
将用户传入的block进行反汇编,然后搜索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 代码 "naturally"在这种情况下。如果事实证明这是错误的,您可以轻松地替换您自己的新异常 class.
正如每个 Ruby 程序员最终发现的那样,调用包含 return
语句的块或过程可能很危险,因为这可能会退出您当前的上下文:
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"
如果您想绝对确定某些代码在 调用块或过程后 运行,您可以使用 begin ... ensure
结构。但是由于如果在 yield 期间出现异常也会调用 ensure
,因此需要多做一些工作。
我创建了一个 tiny module 以两种不同的方式处理这个问题:
使用
safe_yield
,使用return
关键字检测产生的块或proc实际上是否returns。如果是这样,它会引发异常。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
我正在考虑从中创建一个 quick Gem,或者是否有任何内置解决方案来防止我错过的嵌套块中的 quick return?
有一个内置的解决方案可以检测一个块是否包含 return
语句。
可以用RubyVM::InstructionSequence.disasm
将用户传入的block进行反汇编,然后搜索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 代码 "naturally"在这种情况下。如果事实证明这是错误的,您可以轻松地替换您自己的新异常 class.