【问题标题】:Ruby value myseteriously gets lost during exception handlingRuby 值在异常处理期间神秘地丢失
【发布时间】:2022-11-22 05:00:11
【问题描述】:

我已将我的问题浓缩为我认为最小可重现的案例:

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。这里发生了什么?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    现在 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
    

    这里出错时我们抛出符号 :abort_reading 以及返回值 false 所以 catch 块的结果将是 false 当它捕获 :abort_readingtrue 如果 yield 没有导致 AbortReading 错误。

    现在调用puts rfb 的输出是

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-05
      • 1970-01-01
      • 2021-05-15
      • 1970-01-01
      • 2012-12-27
      • 2011-03-10
      相关资源
      最近更新 更多