ruby-concurrency/concurrent-ruby · error · ArgumentError

cannot provide both an observer and a block

Error message

cannot provide both an observer and a block

What it means

IVar#add_observer lets you register a callback for completion: either an observer object plus method symbol, or a bare block. Supplying both an observer object and a block is ambiguous — the library cannot tell which callback to invoke — so it raises ArgumentError 'cannot provide both an observer and a block'.

Source

Thrown at lib/concurrent-ruby/concurrent/ivar.rb:82

        raise ArgumentError.new('provide only a value or a block')
      end
      super(&nil)
      synchronize { ns_initialize(value, opts, &block) }
    end

    # Add an observer on this object that will receive notification on update.
    #
    # Upon completion the `IVar` will notify all observers in a thread-safe way.
    # The `func` method of the observer will be called with three arguments: the
    # `Time` at which the `Future` completed the asynchronous operation, the
    # final `value` (or `nil` on rejection), and the final `reason` (or `nil` on
    # fulfillment).
    #
    # @param [Object] observer the object that will be notified of changes
    # @param [Symbol] func symbol naming the method to call when this
    #   `Observable` has changes`
    def add_observer(observer = nil, func = :update, &block)
      raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
      direct_notification = false

      if block
        observer = block
        func = :call
      end

      synchronize do
        if event.set?
          direct_notification = true
        else
          observers.add_observer(observer, func)
        end
      end

      observer.send(func, Time.now, self.value, reason) if direct_notification
      observer
    end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Use the observer-object form: ivar.add_observer(listener, :update)
  2. Or use the block form only: ivar.add_observer { |time, value, reason| handle(value) }
  3. In adapter code, pick one shape at the boundary and strip the other before calling add_observer

Example fix

# before
ivar.add_observer(listener, :update) { |v| puts v }

# after
ivar.add_observer(listener, :update)
# or
ivar.add_observer { |time, value, reason| puts value }
Defensive patterns

Strategy: validation

Validate before calling

def subscribe(ivar, observer = nil, func = :update, &block)
  raise ArgumentError, 'observer or block, not both' if observer && block
  block ? ivar.add_observer(&block) : ivar.add_observer(observer, func)
end

Try / catch

begin
  ivar.add_observer(observer, :update)
rescue ArgumentError => e
  raise if /observer and a block/.match?(e.message) === false
  # strip one of the two callback shapes and retry once
  ivar.add_observer(observer, :update)
end

Prevention

When it happens

Trigger: ivar.add_observer(listener, :update) { |v| ... } — object plus block; pasting observer-object sample code and adding a block convenience on top; adapters that always pass an observer and also forward any block.

Common situations: Migrating from observer-object style to block style (or vice versa) and leaving both call shapes in place; observer frameworks that wrap user callbacks in objects while the caller also supplies a block.

Related errors


AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21). Data as JSON: /api/errors/d83d7bab61150c38. Report an issue: GitHub.