这是ruby中throw的预期行为吗?

qyyhg6bp  于 2023-08-04  发布在  Ruby
关注(0)|答案(1)|浏览(89)

我在使用trap和catch/throw方法时注意到了这种行为,以便更好地理解它们可以/应该如何使用。
在Ruby中,catch和throw方法似乎是成对使用的:

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

# SIGINT trapped -> throw called -> catch block exits
#   ruby covthrow1p.rb
#   => ........

字符串
或者使用提供给throw方法的第二个参数:

def stop_script
  puts 'CTRL_C seen'
  exit
end

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c, stop_script }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

# SIGINT trapped -> throw called -> catch executes stop_script & exits block
#   ruby covthrow2p.rb
#   => ......CTRL_C seen


如果单独使用throw(naked),只提供key参数,则失败:

def stop_script
  puts 'CTRL_C seen'
  exit
end

trap("SIGINT") { throw :ctrl_c }
(1.. ).each {|n| print "."; sleep 0.5 }

# SIGINT trapped -> throw called -> No catch block -> UncaughtThrowError
#   ruby nkdthrow1p.rb
#   => .......nkdthrow1p.rb:8:in `throw': uncaught throw :ctrl_c (UncaughtThrowError)


但是如果使用裸抛出并提供第二个参数,则会成功!

def stop_script
  puts 'CTRL_C seen'
  exit
end

trap("SIGINT") { throw :ctrl_c, stop_script }
(1.. ).each {|n| print "."; sleep 0.5 }

# SIGINT trapped -> throw called -> No catch block -> call stop_script ???
#   ruby nkdthrow2p.rb
#   => ......CTRL_C seen


这是预期行为吗?一个执行的工件?一个bug?看起来一般是无害的,但可能会导致混乱的行为。
这些示例是在Windows 10上运行的Ruby 3.2.2上。

0kjbasz6

0kjbasz61#

你对第二个例子的解释是错误的。现在让我们注解掉exit,这样我们就可以看到完整的执行过程:

def stop_script
  puts 'CTRL_C seen'
  # exit
end

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c, stop_script }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

字符串
实际发生了什么:

  • catch执行其块
  • trap设置SIGINT处理程序
  • each开始执行,并被键盘中断中断
  • SIGINT处理程序执行
  • throw(:ctrl_c, stop_script)执行;由于throw是一个方法,因此需要在方法调用之前计算其参数
  • Ruby中的stop_script等效于self.stop_script(),如果存在这样的方法,则执行stop_script
  • 打印"CTRL_C seen"puts返回nil
  • 如果没有注解掉exit,脚本将退出
  • 因为没有return,所以stop_script返回最后一个语句的值,即nil
  • throw最终被称为throw(:ctrl_c, nil)
  • catch以值nil退出

您的第四个示例是等效的,只是缺少了第一步和最后一步。
至关重要的是,throw的第二个参数只是作为返回值传递给catch的值。它没有“稍后评估”的魔力:它在调用throw之前求值,就像方法的任何参数一样。
如果throw在脚本到达其自然结束时没有被catch捕获,则将打印一个异常。但是,在上一个示例中,假设执行了exit,脚本随即退出,则不会发生异常情况。

相关问题