21

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
pje
  • 21,801
  • 10
  • 54
  • 70
  • I have never seen code like this. Can you point to a public source file? – Sergio Tulentsev Nov 07 '12 at 22:21
  • @SergioTulentsev: until I can find a source file, here's a blog comment: http://blog.rubybestpractices.com/posts/rklemme/003-The_Universe_between_begin_and_end.html#comment-9011441 – pje Nov 07 '12 at 22:24
  • I don't know how it'd be dangerous, but IMO it's wrong: it should be a method. – Dave Newton Nov 07 '12 at 22:25
  • `begin` isn't needed to add `rescue` and `ensure` as `def` is an implicit `begin`, which means that the example is possibly less surprising to me. – ian Nov 07 '12 at 22:32
  • @Sergio: this construct is useful when you want to use a block of code (which returns a value) as an expression. For example: `result = value1 || value2 || begin; x = 1; x + 1; end` (read ";" as line-feeds). begin/end has other uses, for example to write a do-while block. I wouldn't say it's bad practice, just uncommon. And as @mrbrdo it has no weird behaviour. – tokland Nov 07 '12 at 22:39
  • 1
    @tokland yes, code snippets in the provided link were pretty clear. This contrived example (in the question) is not. :) – Sergio Tulentsev Nov 07 '12 at 22:43
  • @Sergio. Contrived, oh, well, use your imagination! :-) imagine that the code inside the begin/end cannot be written as a single expression, imagine it's heavy to calculate (so you want a short-circuited expression). Try to rewrite the expression I wrote using conditionals, it will be more verbose. – tokland Nov 07 '12 at 22:44
  • @tokland: I'm not saying I still don't understand the purpose. I'm saying that the original snippet is so nonsensical that it threw me off track. :) It would make more sense if it said `# imagine heavy calculation here` – Sergio Tulentsev Nov 07 '12 at 22:52
  • I forgot exactly where I used this, but after looking at that page I remembered it's particularly useful for memoization (@cache ||= begin...). Since then you don't have to write if @cache.nil? ; @cache = ... But anyway I think my answer should clear up the question. – mrbrdo Nov 07 '12 at 22:56
  • @SergioTulentsev: Sorry, I thought you meant my snippet (which wasn't so great either) ;-) Yes, it would be preferable that the assignments inside the begin/end block are used to calculate the last value. @mrbrdo: yeah, the `@var ||= begin ... end` is another typical usage; again, it's a way to get a "lazy expression" (in the sense that it can be short-circuited). – tokland Nov 07 '12 at 23:00
  • @SergioTulentsev: feel free to edit! – pje Nov 07 '12 at 23:01
  • @pje: I believe that one of stackoverflow rules is: "You don't touch man's code". Anyway, I think you got your answer by now :) – Sergio Tulentsev Nov 07 '12 at 23:05

1 Answers1

37

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.

mrbrdo
  • 7,968
  • 4
  • 32
  • 36
  • 1
    This doesn't answer my question. – pje Nov 07 '12 at 22:25
  • Like I said, it's perfectly valid, so there aren't any unintended consequences. It's just a code-style thing. – mrbrdo Nov 07 '12 at 22:27
  • and that's a fine opinion to have, but "there aren't any unintended consequences" isn't in your answer. – pje Nov 07 '12 at 22:30
  • My answer implies there are none, since I did not mention any ^^ Since I said it's just user preference it implies it's not doing anything unexpected. – mrbrdo Nov 07 '12 at 22:32
  • And if you want to convince yourself that it's not doing anything special, just use RubyVM::InstructionSequence::compile("c = begin; a = 5 ; 6 ; end") and you will see there are no special instructions or anything unexpected there at all. (Ruby 1.9.x) begin/end actually doesn't generate any instruction at all, so it has basically no effect. The only thing it does if you use assignment is that it will duplicate the last item on the stack (dup) at the end and assign (setlocal) the variable then. But otherwise it has no effect whatsoever. – mrbrdo Nov 07 '12 at 22:33
  • There, I updated my answer with the actual instructions so you can see. – mrbrdo Nov 07 '12 at 22:39
  • 2
    I usually use parentheses for that purpose. – sawa Nov 07 '12 at 23:19
  • I don't like that for multiple lines, but everyone has their own personal preference. – mrbrdo Nov 07 '12 at 23:21
  • @pje does this answer your question? – mrbrdo Nov 07 '12 at 23:39
  • @mrbrdo: Not completely. Compiled instruction sequences are implementation-dependent, no? Also, "c = begin; a = 5 ; 6 ; end" is a pretty "safe" instance to cherry-pick. I'm asking about gotchas with this construct _in general_. I guess I'd find something like a citation from a language specification on the behavior of this construct more convincing. – pje Nov 08 '12 at 00:03
  • Ruby's syntax is subtle and can cause [unexpected problems](http://stackoverflow.com/a/3680224/1004889) once you start combining multiple otherwise fine constructs. – pje Nov 08 '12 at 00:11
  • What you linked is not something that can be a problem here. While the compiled instruction sequence is specific to MRI1.9, MRI is the de-facto standard so any different behavior is non-standard. And besides I'm pretty sure all other interpreters behave the same way. You're just nit-picking at this point. begin/end doesn't even have its own local variable scope, so it's totally safe in your context. There are NO unexpected consequences whatsoever, I'm sure of it. And I know the internals of Ruby quite well. The only thing you are doing is making the assignment to the last line of begin/end. – mrbrdo Nov 08 '12 at 00:17
  • 2
    Also what you linked is a syntax thing and has nothing to do with behavior. Keep also in mind there is no formal language specification for Ruby. I think I've answered your question, or it's unanswerable. I don't know what exactly you are expecting from the answer. – mrbrdo Nov 08 '12 at 15:41