我已经把我的问题浓缩成我认为是最小可重复的情况:
class AbortReading < RuntimeError; end
class SomeError < RuntimeError; end
def rno
retval = false
catch(:abort_reading) do
begin
yield
rescue AbortReading
puts "throw abort_reading"
throw :abort_reading
end # begin
puts "Setting to true"
retval = true
end # catch
ensure # rno
puts "rno returns #{retval.inspect}"
retval # return value
end
def rfb
success = rno do
begin
puts "failing"
fail SomeError
rescue SomeError
puts "intercepted SomeError"
fail AbortReading
end
end
puts "success=#{success.inspect}"
success
end
puts rfb
我有两个方法,rno
和rfb
。rno
应该接受一个块。它返回true,除非该块引发异常AbortReading
,在这种情况下它返回false。注意throw
的一些不寻常的用法,过早地跳到rno
的末尾;这个结构是从实际的(更复杂的)代码中取来的,在那里它确实有意义,我也在我的示例中使用了它,因为我觉得问题的原因可能在这部分。
方法rfb
使用rno
,在其主体中,它首先引发SomeError
,并将此异常转换为AbortReading
。这个有点奇怪的构造也取自原始实现。
我希望rfb
的调用会导致false
,因为它会导致AbortReading
,然后rno
会从它返回false
。但是,rfb
返回nil
。这意味着rfb
中的变量success
已经被分配。但是它从不接收retval
的值。
运行代码将生成输出
failing
intercepted SomeError
throw abort_reading
rno returns false
success=nil
特别要注意的是,rno
在终止之前确实返回false,但是在rfb
内部,值是nil。这是怎么回事?
1条答案
按热度按时间z9smfwbn1#
现在,
rno
的返回值实际上是catch
块的结果,它是nil
,因为您调用throw :abort_reading
时没有提供返回值。ensure
关键字并不隐式返回它,只是“确保”此代码在方法正常返回之前运行。如果你想让
ensure
返回,你需要显式地使用return
关键字。话虽如此,我不建议这样做,而是利用您可以使用
Kernel#throw
提供返回值的事实,这样我们就可以重构您的代码以这里,如果出现
AbortReading
错误,我们将抛出符号:abort_reading
沿着返回值false
,因此catch
块的结果将是false
(当它捕获:abort_reading
时)或true
(如果yield
未导致AbortReading
错误)。现在,调用
puts rfb
的输出为