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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With