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

  1. Call swap with a literal block: `atom.swap { |v| v + 1 }` — extra arguments come after the value: `atom.swap(2) { |v, step| v + step }`.
  2. When the updater is a Proc or Method object, convert it: `atom.swap(&updater)`.
  3. If you only want an unconditional write, use `atom.reset(new_value)` or `atom.value = new_value` instead of swap.
  4. 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

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.