DEV Community

Takashi SAKAGUCHI
Takashi SAKAGUCHI

Posted on

Avoiding Symbol Block-Pass (&:to_s) in Ruby and Choosing More Readable Alternatives

In Ruby, it’s common to see the shorthand syntax using symbols as block arguments, such as &:to_s.
While this looks concise and elegant, it can be confusing for beginners and isn’t always well supported by IDEs or refactoring tools.

Recently, more intuitive options like it and _1 have been introduced, which raises the question: should teams start unifying their style around them?

This article explores the idea of intentionally avoiding the symbol block-pass syntax and shows a practical approach using RuboCop.

The Evolution of Block Parameters

Ruby has gone through several stages of block parameter evolution.

# All of the following return ["1", "2", "3"]

# The most primitive style
[1, 2, 3].map { |i| i.to_s }

# Since Ruby 1.9
[1, 2, 3].map(&:to_s)

# Since Ruby 2.7 (Numbered Parameters)
[1, 2, 3].map { _1.to_s }

# Since Ruby 3.4 (it parameter)
[1, 2, 3].map { it.to_s }
Enter fullscreen mode Exit fullscreen mode

The primitive style

The classic way requires explicitly naming a block parameter:

[1, 2, 3].map { |i| i.to_s }
Enter fullscreen mode Exit fullscreen mode

Even though the variable name doesn’t matter, you still need to provide one.

Symbol#to_proc style

Ruby 1.9 introduced the shorthand using Symbol#to_proc:

[1, 2, 3].map(&:to_s)
Enter fullscreen mode Exit fullscreen mode

Internally, :to_s.to_proc behaves like this:

sym = :to_s
blk = sym.to_proc

# Roughly equivalent to:
# ->(obj, *args, **kwargs, &block) { obj.public_send(:to_s, *args, **kwargs, &block) }

blk.call(1)  # => "1"
Enter fullscreen mode Exit fullscreen mode

When passed as a block (&:to_s), Ruby implicitly calls to_proc and executes the method.

Numbered Parameters and it

Later came Numbered Parameters (_1) in Ruby 2.7, and the it shorthand in Ruby 3.4, both expressing the idea that “the name doesn’t matter.”

[1, 2, 3].map { _1.to_s }
[1, 2, 3].map { it.to_s }
Enter fullscreen mode Exit fullscreen mode

Other languages also have similar constructs, like Kotlin’s it or Scala’s _.

Why Avoid Symbol Block-Pass?

The motivation behind these newer syntaxes is to make the “namelessness” of the parameter explicit.
Both _1 and it solve this issue, and personally, I prefer it—it reads naturally, it’s the newest addition, and it aligns with current Ruby trends.

The problem with Symbol#to_proc is that non-Ruby developers often struggle to understand it at a glance.
Ruby is fun because it offers multiple ways to write the same thing, but if we want Ruby to stay beginner-friendly and appealing, readability matters.

Also, _1 and it integrate better with IDEs and refactoring tools.

That’s why I believe Symbol#to_proc should be limited to code golf or niche cases, and we should stop using it in everyday production code.

A Custom RuboCop Cop

To enforce this idea, here’s a custom RuboCop cop.
It flags usages like array.map(&:to_s) and suggests replacing them with it (or _1).

It works like the inverse of Style::SymbolProc.

# To enable this custom cop, add the following to `.rubocop.yml`:
#
# require:
#   - path/to/custom/cop/avoid_symbol_block_pass.rb
#
# Custom/AvoidSymbolBlockPass:
#   Enabled: true
#
# --- Offense (NG) ---
# array.map(&:to_s)
# users.each(&:destroy)
#
# --- Allowed (OK) ---
# array.map { it.to_s }
# users.each { it.destroy }

class RuboCop::Cop::Custom::AvoidSymbolBlockPass < RuboCop::Cop::Base
  MSG = "Avoid using Symbol#to_proc (`&:to_s`). Consider using `it` or `_1` instead."

  def on_block_pass(node)
    return unless node.children.first&.sym_type?

    add_offense(node)
  end
end
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • Ruby provides multiple block syntaxes, each with historical context.
  • Symbol#to_proc (&:to_s) is concise, but not beginner-friendly and not IDE-friendly.
  • Prefer clearer alternatives like it (or _1).
  • Use a RuboCop custom cop to enforce this style within your team.

By shifting away from the symbol block-pass syntax, we can make Ruby codebases more approachable, consistent, and easier to maintain.

Top comments (0)