简体   繁体   English

没有将`rescue`用作代码块的Ruby的`begin…end`会有意外的后果吗?

[英]Are there unintended consequences of Ruby's `begin … end` without `rescue` used as a code block?

I occasionally see begin...end blocks used in ruby without any rescue , else , ensure , etc. statements in between. 我偶尔会看到ruby中使用的begin...end块,而没有进行任何rescueelseensure等语句之间。 For example: 例如:

foo = begin
   whatever = 3
   "great"
   42
end

The coder's intent, it seems, is to use the begin...end block just for its block-grouping quality (as if begin were do ). 看来,编码器的意图是仅出于其块分组质量而使用begin...end块(就好像begindo )。 Personally I think this usage kind of violates the principle of least surprise ( begin implies exception-handling to me). 我个人认为这种用法违反了最少惊讶的原则( begin对我意味着异常处理)。

Are there any unintended consequences of using begin...end in this way? 以这种方式使用begin...end有任何意外的后果吗? Do begin...end blocks have any semantic differences (maybe in exception-handling?) that make this usage dangerous? begin...end块是否有任何语义差异(可能在异常处理中?),使这种用法很危险?

Ruby's syntax is unbelievably subtle, and I wouldn't be surprised if there were weird gotchas lying in wait here. Ruby的语法令人难以置信,如果在这里等待等待,我就不会感到惊讶。

I use this sometimes if I want to assign something to a variable but I have to calculate the value I want to assign first. 如果我想给变量赋值,但有时我必须先计算要赋值,我有时会用到它。 It makes the code a little bit more tidy this way. 这样可以使代码更加整洁。 I think it's user preference. 我认为这是用户的偏爱。 Basically you are saying: I am assigning something to foo, but in order to get the value I want I first need to do some things. 基本上您是在说:我为foo分配了一些东西,但是为了获得所需的值,我首先需要做一些事情。 It's particularly useful when doing memoization, so instead of 做备忘录时特别有用,因此

if @cache.nil?
  do_something!
  @cache = read_value
end

You can do 你可以做

@cache ||= begin
  do_something!
  read_value
end

What you are taking advantage here is that the Ruby interpreter has a stack, and each expression will usually push something on the stack, or take something from the stack. 您在这里利用的是Ruby解释器有一个堆栈,每个表达式通常会将某些内容压入堆栈或从堆栈中取出内容。 Assignment just takes the last thing from the stack and assigns it (in this case the last line from begin/end). 分配只是从堆栈中取出最后一个东西并分配它(在这种情况下,这是开始/结束的最后一行)。 Many times knowing this (stack approach in Ruby) can be useful. 很多时候了解这一点(Ruby中的堆栈方法)可能会很有用。

I don't think it violates least surprise though, I think it's user preference wheather you want to use it or not. 我认为它不会给您带来什么意外的惊喜,我认为您是否愿意使用它都是用户的首选。

You can see that it doesn't do anything unexpected by looking at what bytecode instructions it generates in Ruby MRI 1.9: 通过查看它在Ruby MRI 1.9中生成的字节码指令,您可以看到它没有做任何意外的事情:

 RubyVM::InstructionSequence::compile("c = begin; a = 5; 6; end").to_a

 [:trace, 1],
 [:trace, 1],
 [:putobject, 5],
 [:setlocal, 2],
 [:trace, 1],
 [:putobject, 6],
 [:dup],
 [:setlocal, 3],
 [:leave]

Trace is just for stack traces, you can ignore that. 跟踪仅用于堆栈跟踪,您可以忽略它。 Dup duplicates the last item on the stack. Dup复制堆栈中的最后一个项目。 In this example the number of the local variable a is 2 and the number of the local variable c is 3 (hence putobject, 2 will assign to variable a , etc). 在此示例中,局部变量a的数量为2 ,局部变量c的数量为3 (因此putobject, 2将分配给变量aputobject, 2 )。 The only side-effect of this compared to a = 5; c = 6 a = 5; c = 6相比,其唯一的副作用a = 5; c = 6 a = 5; c = 6 is the dup instruction, which means the stack size of your method will be larger by 1 slot. a = 5; c = 6dup指令,这意味着方法的堆栈大小将增加1个插槽。 But this is not particularly important because it only has any effect while the interpreter is inside this particular method, and memory for stack is pre-reserved anyway, so it only means the stack pointer will be decremented by 1 more than it would otherwise. 但这并不是特别重要,因为它仅在解释器位于此特定方法内部时才起作用,并且无论如何都将为堆栈保留内存,因此这仅意味着堆栈指针将比原来减少1。 So basically no change at all. 因此,基本上没有任何变化。 With optimizations turned on even the dup will probably disappear. 启用优化后,即使dup也可能消失。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM