ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
Inside a Channel.select block, sel.take(channel) registers a take clause whose block is invoked with the received value when the clause wins. Without a block the clause could never deliver its result, so registration raises ArgumentError.
Source
Thrown at lib/concurrent-ruby-edge/concurrent/channel/selector.rb:30
class Selector
def initialize
@clauses = []
@error_handler = nil
end
def case(channel, action, message = nil, &block)
if [:take, :poll, :receive, :~].include?(action)
take(channel, &block)
elsif [:put, :offer, :send, :<<].include?(action)
put(channel, message, &block)
else
raise ArgumentError.new('invalid action')
end
end
def take(channel, &block)
raise ArgumentError.new('no block given') unless block_given?
@clauses << TakeClause.new(channel, block)
end
alias_method :receive, :take
def put(channel, message, &block)
@clauses << PutClause.new(channel, message, block)
end
alias_method :send, :put
def after(seconds, &block)
@clauses << AfterClause.new(seconds, block)
end
alias_method :timeout, :after
def default(&block)
raise ArgumentError.new('no block given') unless block_given?
@clauses << DefaultClause.new(block)
endView on GitHub (pinned to 0b88d5ff75)
Solutions
- Always pass the result block: sel.take(ch) { |value| handle(value) }.
- When registering many takes, build the block in the loop so each clause has one: channels.each { |ch| sel.take(ch) { |v| handle(ch, v) } }.
- Validate handler presence before building the selector when wiring from config.
Example fix
# before
channels.each { |ch| sel.take(ch) } # no block -> raises
# after
channels.each { |ch| sel.take(ch) { |value| handle(ch, value) } } Defensive patterns
Strategy: validation
Validate before calling
channels.each do |ch| raise ArgumentError, 'handler block missing' unless handler sel.take(ch, &handler) end
Prevention
- Every clause registrar (take/put/after/default/error) needs a block - treat it as part of the DSL signature.
- When building clauses from config, validate handler presence before entering Channel.select.
- Prefer constructing blocks inline in loops rather than passing possibly-nil variables with &.
When it happens
Trigger: sel.take(ch) with no block in a select DSL - typically programmatic registration (channels.each { |ch| sel.take(ch) }) that forgot the block, or sel.take(ch, &nil) via a nil handler variable.
Common situations: Registering clauses in a loop over a channel list and dropping the per-clause block; refactoring a literal block into a variable that ends up nil; building selectors from config where the handler key is missing.
Related errors
- no block given
- invalid action
- unbuffered channels cannot have a capacity
- capacity must be at least 1 for this buffer type
- size must be greater than 0
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/b68267812b97729a.
Report an issue: GitHub.