Skip to content

Instantly share code, notes, and snippets.

@mudge
Last active November 3, 2021 08:59
Show Gist options
  • Save mudge/786953 to your computer and use it in GitHub Desktop.
Save mudge/786953 to your computer and use it in GitHub Desktop.
How to pass blocks between methods in Ruby < 3.0.0 without using &block arguments.
# UPDATE: The ability to call Proc.new without a block was removed in Ruby 3.0.0
# See https://bugs.ruby-lang.org/issues/10499 for more information.
# So you might have heard that this is slow:
#
# def some_method(&block)
# block.call
# end
#
# Compared to:
#
# def some_method
# yield
# end
#
# But how can you pass a block from one method to another without
# using the &block argument?
#
# def some_method(&block)
# some_other_method(&block)
# end
#
# Behold!
def some_method
some_other_method(&Proc.new)
end
def some_other_method
puts yield * 2
end
some_method { "Magic!" }
# => "Magic!Magic!"
# As mentioned by Aaron Patterson in his RubyConf X presentation
# "ZOMG WHY IS THIS CODE SO SLOW", this means that you can avoid the cost
# of instantiating a Proc object altogether if suitable.
def some_method
if block_given?
some_other_method(&Proc.new)
end
end
# The key to this is in Proc.new which will create a Proc object from the
# block passed to the current method when called without any arguments of
# its own.
# For more information, see the following:
# http://www.ruby-doc.org/core/classes/Proc.html#M000547
# http://confreaks.net/videos/427-rubyconf2010-zomg-why-is-this-code-so-slow (at around 30:00)
@michael-reeves
Copy link

Thanks, this is exactly what I needed to DRY out some code. :)

@LuisSol
Copy link

LuisSol commented Mar 20, 2020

Thanks Bro this save me a lot of troubles

@zauzaj
Copy link

zauzaj commented May 5, 2020

Interesting!
What if you have block as an optional argument? With &Proc.new it will raise, so here using &block is a must right?

For example:
some_other_method(arg1, arg2, &Proc.new)
And if you call some_method without a block it still should execute.

@mudge
Copy link
Author

mudge commented May 6, 2020

@zauzaj I wrote about this in a bit more detail in "Passing Blocks in Ruby Without &block" but, yes, you'd need to detect whether an optional block was passed with block_given? before calling Proc.new in that situation.

@kopylovvlad
Copy link

The code does not work with Ruby-3.0.1. The error is "tried to create Proc object without a block (ArgumentError)"

@mudge
Copy link
Author

mudge commented Jul 1, 2021

@kopylovvlad you're right, it looks like this was changed in Ruby 3.0.0 (see #10499 and #15554) but the documentation still describes the old behaviour though this also looks like it has been fixed.

I've added a comment to the gist to flag this change of behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment