ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
Concurrent.dataflow, dataflow!, dataflow_with and dataflow_with! all funnel into the private call_dataflow, which requires a block describing the computation to run once every input IVar resolves. It raises ArgumentError('no block given') when that block is missing. This is a fail-fast API contract check at call time, not a concurrency failure.
Source
Thrown at lib/concurrent-ruby/concurrent/dataflow.rb:58
call_dataflow(:value, executor, *inputs, &block)
end
module_function :dataflow_with
def dataflow!(*inputs, &block)
dataflow_with!(Concurrent.global_io_executor, *inputs, &block)
end
module_function :dataflow!
def dataflow_with!(executor, *inputs, &block)
call_dataflow(:value!, executor, *inputs, &block)
end
module_function :dataflow_with!
private
def call_dataflow(method, executor, *inputs, &block)
raise ArgumentError.new('an executor must be provided') if executor.nil?
raise ArgumentError.new('no block given') unless block_given?
unless inputs.all? { |input| input.is_a? IVar }
raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }")
end
result = Future.new(executor: executor) do
values = inputs.map { |input| input.send(method) }
block.call(*values)
end
if inputs.empty?
result.execute
else
counter = DependencyCounter.new(inputs.size) { result.execute }
inputs.each do |input|
input.add_observer counter
end
endView on GitHub (pinned to 0b88d5ff75)
Solutions
- Pass the computation as a literal block: Concurrent.dataflow(f1, f2) { |a, b| a + b }
- When the callable is in a variable, pass it with &: Concurrent.dataflow(f1, &handler)
- In wrapper methods, check block_given? before delegating and raise your own descriptive error
- When dispatching dynamically, capture the block as &proc and forward it intact
Example fix
# before
Concurrent.dataflow(future_a, future_b, ->(a, b) { a + b })
# after
Concurrent.dataflow(future_a, future_b) { |a, b| a + b } Defensive patterns
Strategy: validation
Validate before calling
fail ArgumentError, 'dataflow requires a computation block' unless task.respond_to?(:call) Concurrent.dataflow(*inputs, &task)
Try / catch
begin
Concurrent.dataflow(*inputs) { |*vs| compute(*vs) }
rescue ArgumentError => e
raise unless e.message == 'no block given'
raise ArgumentError, "dataflow called without a block from #{caller.first}"
end Prevention
- Pass callables with & whenever they come from a variable
- Forward &block explicitly in every wrapper around dataflow
- Treat a missing computation as a programming error: fail fast in your own API with caller context
When it happens
Trigger: Calling Concurrent.dataflow(f1, f2) with no block; passing the computation as a positional argument instead of a block (Concurrent.dataflow(f1, ->(v) { v + 1 })); delegating through your own wrapper method that takes &block but whose caller supplied none; dynamic dispatch (define_method/instance_exec) that drops the block.
Common situations: Refactoring drops the trailing block; the callable is stored in a local and passed without the & sigil; a DSL wrapper makes the block optional while dataflow requires it; copy-pasted example code using method objects where no block reaches dataflow.
Related errors
- Not all dependencies are IVars. Dependencies: #{ inputs.insp
- no block given
- no block given
- no block given
- no block given
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/f366900173450e8a.
Report an issue: GitHub.