ruby-concurrency/concurrent-ruby · error · ArgumentError
no block given
Error message
no block given
What it means
Concurrent::Atom#swap atomically replaces the atom's value with the result of a block that receives the current value (plus any passed arguments) and returns the intended new value. The block is the update function itself, so swap raises ArgumentError immediately when called without one, before the compare-and-set loop starts.
Source
Thrown at lib/concurrent-ruby/concurrent/atom.rb:158
# of side effects.
#
# @note The given block may be called multiple times, and thus should be free
# of side effects.
#
# @param [Object] args Zero or more arguments passed to the block.
#
# @yield [value, args] Calculates a new value for the atom based on the
# current value and any supplied arguments.
# @yieldparam value [Object] The current value of the atom.
# @yieldparam args [Object] All arguments passed to the function, in order.
# @yieldreturn [Object] The intended new value of the atom.
#
# @return [Object] The final value of the atom after all operations and
# validations are complete.
#
# @raise [ArgumentError] When no block is given.
def swap(*args)
raise ArgumentError.new('no block given') unless block_given?
loop do
old_value = value
new_value = yield(old_value, *args)
begin
break old_value unless valid?(new_value)
break new_value if compare_and_set(old_value, new_value)
rescue
break old_value
end
end
end
# Atomically sets the value of atom to the new value if and only if the
# current value of the atom is identical to the old value and the new
# value successfully validates against the (optional) validator given
# at construction.
#View on GitHub (pinned to 0b88d5ff75)
Solutions
- Call swap with a literal block: `atom.swap { |v| v + 1 }` — extra arguments come after the value: `atom.swap(2) { |v, step| v + step }`.
- When the updater is a Proc or Method object, convert it: `atom.swap(&updater)`.
- If you only want an unconditional write, use `atom.reset(new_value)` or `atom.value = new_value` instead of swap.
- Audit helper methods: every def that forwards to swap must capture and forward the block (`def bump(&blk) atom.swap(&blk) end`).
Example fix
// before
updater = ->(v) { v + 1 }
atom.swap(updater) # ArgumentError: no block given
// after
atom.swap(&updater) # or: atom.swap { |v| v + 1 } Defensive patterns
Strategy: validation
Validate before calling
raise ArgumentError, 'swap requires an updater block' unless block_given?
atom.swap { |v| v + 1 } Type guard
def callable_updater?(obj) obj.respond_to?(:call) end atom.swap(&updater) if callable_updater?(updater)
Try / catch
begin
atom.swap(&updater)
rescue ArgumentError => e
raise unless e.message == 'no block given'
logger.error('Atom#swap called without a block')
raise
end Prevention
- Always convert stored Proc/Method updaters with & when calling swap or try_update.
- Use atom.value = for plain writes; reserve swap for read-modify-write cycles.
- Treat the block as part of swap's signature in any wrapper: def bump(&blk) atom.swap(&blk) end.
When it happens
Trigger: `atom.swap` with no block; passing a Proc as a positional argument `atom.swap(updater)` instead of as a block `atom.swap(&updater)`; helper methods that accept an updater but forget to forward it with `&`.
Common situations: Refactoring from `atom.value = compute` to a compare-and-set swap; dynamic updater selection where the chosen Proc is dropped; metaprogramming (define_method/instance_exec) losing the block; code copied from examples that omit the block.
Related errors
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/098a35686444000d.
Report an issue: GitHub.