简体   繁体   中英

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. 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 ). Personally I think this usage kind of violates the principle of least surprise ( begin implies exception-handling to me).

Are there any unintended consequences of using begin...end in this way? Do begin...end blocks have any semantic differences (maybe in exception-handling?) that make this usage dangerous?

Ruby's syntax is unbelievably subtle, and I wouldn't be surprised if there were weird gotchas lying in wait here.

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. 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. 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.

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:

 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. 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). The only side-effect of this compared to 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. 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. So basically no change at all. With optimizations turned on even the dup will probably disappear.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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