Ruby值在异常处理过程中神秘丢失

anhgbhbe  于 2022-11-22  发布在  Ruby
关注(0)|答案(1)|浏览(190)

我已经把我的问题浓缩成我认为是最小可重复的情况:

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

我有两个方法,rnorfbrno应该接受一个块。它返回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。这是怎么回事?

z9smfwbn

z9smfwbn1#

现在,rno的返回值实际上是catch块的结果,它是nil,因为您调用throw :abort_reading时没有提供返回值。
ensure关键字并不隐式返回它,只是“确保”此代码在方法正常返回之前运行。
如果你想让ensure返回,你需要显式地使用return关键字。

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}"
  return retval # return value
end

话虽如此,我不建议这样做,而是利用您可以使用Kernel#throw提供返回值的事实,这样我们就可以重构您的代码以

def rno
  retval = catch(:abort_reading) do
    begin
      yield
      puts "Setting to true"
      true
    rescue AbortReading 
      puts "throw abort_reading"
      throw :abort_reading, false
    end # begin
  end # catch
ensure # rno
  puts "rno returns #{retval.inspect}"
end

这里,如果出现AbortReading错误,我们将抛出符号:abort_reading沿着返回值false,因此catch块的结果将是false(当它捕获:abort_reading时)或true(如果yield未导致AbortReading错误)。
现在,调用puts rfb的输出为

failing
intercepted SomeError
throw abort_reading
rno returns false
success=false
false

相关问题