ruby-concurrency/concurrent-ruby · error · ArgumentError
Cannot use both value and block as default value
Error message
Cannot use both value and block as default value
What it means
`Concurrent::FiberLocalVar.new(default = nil, &default_block)` accepts exactly one default mechanism: a static value or a block evaluated per fiber on first read there. The guard is `default && block_given?`, so a truthy default plus a block raises ArgumentError, while `nil` (or `false`) plus a block is silently allowed and the block wins. The conflict is rejected because the resolution order between a static value and a computed block would be ambiguous.
Source
Thrown at lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb:51
#
# Fiber.new do
# v.value #=> 14
# v.value = 2
# v.value #=> 2
# end.resume
#
# v.value #=> 14
class FiberLocalVar
LOCALS = FiberLocals.new
# Creates a fiber local variable.
#
# @param [Object] default the default value when otherwise unset
# @param [Proc] default_block Optional block that gets called to obtain the
# default value for each fiber
def initialize(default = nil, &default_block)
if default && block_given?
raise ArgumentError, "Cannot use both value and block as default value"
end
if block_given?
@default_block = default_block
@default = nil
else
@default_block = nil
@default = default
end
@index = LOCALS.next_index(self)
end
# Returns the value in the current fiber's copy of this fiber-local variable.
#
# @return [Object] the current value
def value
LOCALS.fetch(@index) { default }View on GitHub (pinned to 0b88d5ff75)
Solutions
- Pick one mechanism: `FiberLocalVar.new { per_fiber_default }` for computed defaults or `FiberLocalVar.new(value)` for constants.
- In wrappers, forward only the winner: `blk ? FiberLocalVar.new(&blk) : FiberLocalVar.new(value)`.
- Remember the asymmetry: only truthy values conflict — `nil`/`false` with a block quietly uses the block, which can mask intent.
Example fix
// before
req_id = Concurrent::FiberLocalVar.new('none') { Fiber.current.object_id.to_s(36) }
// after
req_id = Concurrent::FiberLocalVar.new { Fiber.current.object_id.to_s(36) } Defensive patterns
Strategy: validation
Validate before calling
def fiber_local(default = nil, &blk) raise ArgumentError, 'default value or block, not both' if default && blk Concurrent::FiberLocalVar.new(default, &blk) end
Prevention
- Choose a static default or a per-fiber block, never both.
- Note nil/false plus a block is silently allowed (block wins) — be explicit anyway.
- Wrap construction in helpers that enforce the either/or rule.
When it happens
Trigger: `Concurrent::FiberLocalVar.new('n/a') { Fiber.current.object_id }` — truthy string plus block. Configuration DSLs that always capture a block while also passing a fallback positional. Wrappers forwarding both `default` and `&blk` from their own signature.
Common situations: A config object that gained a per-fiber block default later while keeping the old value argument; copy-paste between ThreadLocalVar and FiberLocalVar where both were being 'combined'; helpers that accept `value = nil, &blk` and forward both unconditionally.
Related errors
- Cannot use both value and block as default value
- unbuffered channels cannot have a capacity
- capacity must be at least 1 for this buffer type
- no block given
- size must be greater than 0
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/85ad3db93bb5322a.
Report an issue: GitHub.