do..end vs curly braces for blocks in Ruby

53,084

Solution 1

The general convention is to use do..end for multi-line blocks and curly braces for single line blocks, but there is also a difference between the two that can be illustrated with this example:

puts [1,2,3].map{ |k| k+1 }
2
3
4
=> nil
puts [1,2,3].map do |k| k+1; end
#<Enumerator:0x0000010a06d140>
=> nil

This means that {} has a higher precedence than do..end, so keep that in mind when deciding what you want to use.

One more example to keep in mind while you develop your preferences.

The following code:

task :rake => pre_rake_task do
  something
end

really means:

task(:rake => pre_rake_task){ something }

And this code:

task :rake => pre_rake_task {
  something
}

really means:

task :rake => (pre_rake_task { something })

So to get the actual definition that you want, with curly braces, you must do:

task(:rake => pre_rake_task) {
  something
}

Maybe using braces for parameters is something you want to do anyways, but if you don't it's probably best to use do..end in these cases to avoid this confusion.

Solution 2

From Programming Ruby:

Braces have a high precedence; do has a low precedence. If the method invocation has parameters that are not enclosed in parentheses, the brace form of a block will bind to the last parameter, not to the overall invocation. The do form will bind to the invocation.

So the code

f param {do_something()}

Binds the block to the param variable while the code

f param do do_something() end

Binds the block to the function f.

However this is a non-issue if you enclose function arguments in parenthesis.

Solution 3

There a few points of view on this, it's really a matter of personal preference. Many rubyists take the approach you do. However, two other styles that are common is to always use one or the other, or to use {} for blocks that return values, and do ... end for blocks that are executed for side effects.

Solution 4

There is one major benefit to curly braces - many editors have a MUCH easier time of matching them, making certain types of debugging much easier. Meanwhile, the keyword "do...end" is quite a bit harder to match, especially since "end"s also match "if"s.

Solution 5

The most common rule I've seen (most recently in Eloquent Ruby) is:

  • If it's a multi-line block, use do/end
  • If it's a single line block, use {}
Share:
53,084

Related videos on Youtube

Blake Taylor
Author by

Blake Taylor

I love Rails and develop with it at AgLocal.com. As of late I've also been paying attention to Coffeescript, Backbone, Node, Neo4j and Clojure. I get excited about interesting and effective processes, having my computer do things for me, writing programs about programs, and the design of all good things.

Updated on February 26, 2022

Comments

  • Blake Taylor
    Blake Taylor about 2 years

    I have a coworker who is actively trying to convince me that I should not use do..end and instead use curly braces for defining multiline blocks in Ruby.

    I'm firmly in the camp of only using curly braces for short one-liners and do..end for everything else. But I thought I would reach out to the greater community to get some resolution.

    So which is it, and why? (Example of some shoulda code)

    context do
      setup { do_some_setup() }
      should "do somthing" do
        # some more code...
      end
    end
    

    or

    context {
      setup { do_some_setup() }
      should("do somthing") {
        # some more code...
      }
    }
    

    Personally, just looking at the above answers the question for me, but I wanted to open this up to the greater community.

    • Daniel Lee
      Daniel Lee about 13 years
      Just wondering but what are your coworkers' arguments for using braces? Seems more like a personal preference than a logic thing.
    • halfdan
      halfdan about 13 years
      If you want a discussion make it a community wiki. To your question: It's just personal style. I prefer the curly braces as they look more nerdy.
    • PJP
      PJP about 13 years
      It isn't a preference thing, there are syntactic reasons for using one over the other in addition to stylistic reasons. Several of the answers explain why. Failing to use the right one can cause very subtle bugs that are hard to find if another "stylistic" choice is made to never use wrapping parenthesis for methods.
    • Blake Taylor
      Blake Taylor about 13 years
      Wow, some great answers, I didn't expect so much. For my own sake, I want to point out I wasn't oblivious to the precedence difference. I just believe an edge condition that would only effect the outcome of 1% of my code shouldn't have that great of an effect on how I style the other 99%. When and if it mattered, I'd make exception from whatever style detected.
    • PJP
      PJP about 13 years
      "Edge conditions" have a bad habit of sneaking up on people who don't know about them. Coding defensively means a lot of things, including deciding to use coding styles that minimize the chance of ever running into the cases. YOU might be aware, but the guy two people after you might not be after the tribal knowledge has been forgotten. It tends to happen in corporate environments unfortunately.
    • Blake Taylor
      Blake Taylor about 13 years
      @tinman These types of edge conditions are handled by TDD. This is why Rubist can write code like this and get a full nights rest knowing such errors don't exists in their code. When developing with TDD or BDD and such a mistake in precedence is made the red shown on the screen is what reminds us of the "tribal knowledge". Usually the solution is to add a couple of parens somewhere which is totally acceptable within the standard conventions. :)
    • awendt
      awendt about 11 years
      @BlakeTaylor Sorry, but saying that these cases are handled by TDD is just not true. I just got bitten by this in a test and spent half an hour debugging it :(
    • Cees Timmerman
      Cees Timmerman over 3 years
      Does this answer your question? Using do block vs braces {}
  • PJP
    PJP about 13 years
    It is more than preference. The binding between the two is different and can cause problems that are hard to diagnose. Several of the answers detail the problem.
  • PJP
    PJP about 13 years
    It isn't a matter of personal preference only. Binding of parameters can cause subtle bugs if method params aren't enclosed in parenthesis.
  • Kelvin
    Kelvin almost 13 years
    In terms of people of other language backgrounds - I think the difference in "understandability" is so small in this case as to not warrant consideration. (That wasn't my downvote btw.)
  • ck3g
    ck3g over 12 years
    RubyMine for example, highlight do ... end keywords by default
  • ben
    ben over 9 years
    Why does puts [1,2,3].map do |k| k+1; end only print the enumerator i.e. one thing. Isn't puts passed two arguments here namely [1,2,3].map and do |k| k+1; end?
  • bonyiii
    bonyiii over 8 years
    github.com/bbatsov/rubocop/issues/1520 f = lambda do |param| ... end scope :some_scope, f Maybe this looks unidiomatic, and then there's the other alternative, which I thinks is ugly: scope :some_scope, (lambda do |param| ... end)
  • Chinoto Vokro
    Chinoto Vokro about 8 years
    While I do prefer braces, I see no reason that an editor would have trouble finding a 2/3 character string versus a single character. Though there is the argument that most editors already match braces, whereas do ... end is a special case which requires language specific logic.
  • Kelvin
    Kelvin about 8 years
    Strictly speaking, there is another "if" syntax - the postfix "if" (do_stuff if x==1), which is kind of annoying to convert to the regular syntax. But that's another discussion.
  • Eido95
    Eido95 almost 8 years
    [1,2,3].map do |k| puts k+1 end seems more readable then puts [1,2,3].map { |k| k+1 }
  • karatedog
    karatedog over 7 years
    @ben: exactly, because it is puts that gets two parameters. First .map gets evaluated, but it did not get a block (as the thing that looks like a block is not .map's argument), so it returns an enumerator which becomes puts first argument. And puts will get that block as 2nd argument, but puts does not use any passed block, so there happens nothing. Parentheses fix this problem: puts ([1,2,3].map do |k| k+1; end)
  • Mecki
    Mecki about 5 years
    There are prefix if, postfix if, postfix unless (also a kind of if, a if-not) and one line if with then. The Ruby way is to have plenty of ways to express in fact something that is always the same thing, much worse than what C is offering that follows very simple, strict rules.