# Do/end block behavior differs from Ruby

**URL:** <https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545>\
**Category:** Help & Support\
**Created:** [December 20, 2024, 3:10am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545 "2024-12-20T03:10:37Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![keidax](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/keidax/32/2729_2.png) [@keidax](https://forum.crystal-lang.org/u/keidax)\
**Post date:** [December 20, 2024, 3:10am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/1 "2024-12-20T03:10:37Z")

</div>

From [the documentation](https://crystal-lang.org/reference/1.14/syntax_and_semantics/blocks_and_procs.html):

> The difference between using `do ... end` and `{ ... }` is that `do ... end` binds to the left-most call, while `{ ... }` binds to the right-most call

However, this doesn’t match the actual behavior I see:

```auto
def a(*args) end
def a(*args)
  puts "a received block"
  yield
end
def b(*args) end
def b(*args)
  puts "b received block"
  yield
end
def c(*args) end
def c(*args)
  puts "c received block"
  yield
end

a b c do
  puts "in block"
end

```

The output is:

```auto
b received block
in block

```

To demonstrate further: [Carcin](https://carc.in/#/r/hiyf)

Rather than binding to the left-most call, it seems do/end blocks bind to the second-from-the-right call. Is this behavior intended?

For comparison, here is an equivalent Ruby snippet:

```ruby
def a(*args)
  if block_given?
    puts "a received block"
    yield
  end
end

def b(*args)
  if block_given?
    puts "b received block"
    yield
  end
end

def c(*args)
  if block_given?
    puts "c received block"
    yield
  end
end

a b c do
  puts "in block"
end

```

The output of the ruby snippet is

```auto
a received block
in block

```

---

<div class="post-metadata">

**Author:** ![keidax](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/keidax/32/2729_2.png) [@keidax](https://forum.crystal-lang.org/u/keidax)\
**Post date:** [December 20, 2024, 3:24am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/2 "2024-12-20T03:24:07Z")

</div>

Also, for context, I’m working on [GitHub - crystal-lang-tools/tree-sitter-crystal: tree sitter parser for crystal lang, fork of keidax/tree-sitter-crystal](https://github.com/crystal-lang-tools/tree-sitter-crystal). The parser currently assumes Crystal’s block precedence rules match Ruby, and I just realized it doesn’t 😱

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [December 20, 2024, 6:59am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/3 "2024-12-20T06:59:50Z")

</div>

wired behavior:

```crystal
def a(*args)
  puts "a"
end

def a(*args, &)
  puts "a received block"
  yield
end

def b(*args)
  puts "b"
end

def b(*args, &)
  puts "b received block"
  yield
end

def c(*args)
  puts "c"
end

def c(*args, &)
  puts "c received block"
  yield
end

a b c do
  puts "in block"
end

```

```auto
c
b received block
in block
a

```

If this code works on ruby, `do ... end` block should bind to the `a`, but if replace with `{...}`, it bind to `c`.

But, Crystal binding to `b`.

---

<div class="post-metadata">

**Author:** ![yxhuvud](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/yxhuvud/32/27_2.png) [@yxhuvud](https://forum.crystal-lang.org/u/yxhuvud)\
**Post date:** [December 20, 2024, 11:36am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/4 "2024-12-20T11:36:52Z")

</div>

I’d report it on the github issue tracker, as at the very least it is a bug in the documentation. Though the behavior seems weird so it is probably not intended.

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [December 20, 2024, 11:59am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/5 "2024-12-20T11:59:26Z")

</div>

I always felt this difference in behaviour is quite extraordinary. And probably more confusing than helpful. 🤷

It’s a good recommendation to always use parenthesis when there is doubt. Maybe the formatter should do that? 🤔

Anyway while looking briefly at the parser code, it seems obvious why this is failing: To work correctly as described, this feature would require backtracking through a chain of calls in order to attach `do` to the leftmost one. But the parser doesn’t do that. It’s just a simple boolean flag that indicates to stop parsing the next call on a `do` keyword. Then the previous call continues.  
So tentatively a fix could turn that boolean into a counter to keep track of multiple nested calls.

Btw. in the source code I noticed another quirk: parentheses breaks the left binding:

```cr
a (1; 2), b do
   puts "in block"
end

```

```auto
b received block
in block

```

---

<div class="post-metadata">

**Author:** ![keidax](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/keidax/32/2729_2.png) [@keidax](https://forum.crystal-lang.org/u/keidax)\
**Post date:** [December 21, 2024, 1:14am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/6 "2024-12-21T01:14:41Z")

</div>

Github issue created: [Possible bug: binding of do/end blocks is surprising and doesn't match the documentation · Issue #15303 · crystal-lang/crystal · GitHub](https://github.com/crystal-lang/crystal/issues/15303)

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [December 21, 2024, 3:45am UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/7 "2024-12-21T03:45:31Z")

</div>

What a great find!

---

<div class="post-metadata">

**Author:** ![keidax](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/keidax/32/2729_2.png) [@keidax](https://forum.crystal-lang.org/u/keidax)\
**Post date:** [January 26, 2025, 4:00pm UTC](https://forum.crystal-lang.org/t/do-end-block-behavior-differs-from-ruby/7545/8 "2025-01-26T16:00:52Z")

</div>

I’m still hoping to get some feedback on the Github issue. I’m working on the tree-sitter implementation for Crystal, and need to reconsider some choices about how block precedence is handled. Knowing whether this behavior might change in the future would be very helpful!
