ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
Concurrent::Delay defers a computation until #value is first called; the block given to new IS that computation, so it is mandatory. initialize raises ArgumentError('no block given') when the Delay is constructed without one, because a Delay with nothing to compute is meaningless.
Source
Thrown at lib/concurrent-ruby/concurrent/delay.rb:63
include Concern::Obligation
# NOTE: Because the global thread pools are lazy-loaded with these objects
# there is a performance hit every time we post a new task to one of these
# thread pools. Subsequently it is critical that `Delay` perform as fast
# as possible post-completion. This class has been highly optimized using
# the benchmark script `examples/lazy_and_delay.rb`. Do NOT attempt to
# DRY-up this class or perform other refactoring with running the
# benchmarks and ensuring that performance is not negatively impacted.
# Create a new `Delay` in the `:pending` state.
#
# @!macro executor_and_deref_options
#
# @yield the delayed operation to perform
#
# @raise [ArgumentError] if no block is given
def initialize(opts = {}, &block)
raise ArgumentError.new('no block given') unless block_given?
super(&nil)
synchronize { ns_initialize(opts, &block) }
end
# Return the value this object represents after applying the options
# specified by the `#set_deref_options` method. If the delayed operation
# raised an exception this method will return nil. The exception object
# can be accessed via the `#reason` method.
#
# @param [Numeric] timeout the maximum number of seconds to wait
# @return [Object] the current value of the object
#
# @!macro delay_note_regarding_blocking
def value(timeout = nil)
if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?
super
else
# this function has been optimized for performance andView on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass the computation as a block: Concurrent::Delay.new { expensive_call }
- Pass a stored callable with &: Concurrent::Delay.new(&task)
- In factories, fail fast with raise unless task.respond_to?(:call) before constructing
Example fix
# before
delay = Concurrent::Delay.new(load_config)
# after
delay = Concurrent::Delay.new { load_config } Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, 'task must respond to #call' unless task.respond_to?(:call) Concurrent::Delay.new(&task)
Try / catch
begin
Concurrent::Delay.new { compute }
rescue ArgumentError => e
raise unless e.message == 'no block given'
Concurrent::Delay.new { default_value }
end Prevention
- Construct Delay with a literal block whenever possible
- Route variable callables through &task
- In factories, reject non-callable tasks before reaching Delay.new
When it happens
Trigger: Concurrent::Delay.new with no block at all; Concurrent::Delay.new(timeout: 5) passing only options; passing the task as a positional argument (Concurrent::Delay.new(my_proc)) instead of Concurrent::Delay.new(&my_proc).
Common situations: Factory methods build a Delay from a stored proc and forget the &; the chosen task is conditionally nil; refactoring an eager call into lazy evaluation and dropping the block in the process.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/5c21a682cc13b84c.
Report an issue: GitHub.