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
CopyOnWriteObserverSet is the second observer-registry implementation behind Concurrent::Observable (it swaps the whole observers hash atomically on change instead of notifying over a copy). Its add_observer has the same contract as the other sets: register an observer object (optional callback method, default :update) or a block, never both — the combination is ambiguous and raises ArgumentError.
Source
Thrown at lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb:23
# A thread safe observer set implemented using copy-on-write approach:
# every time an observer is added or removed the whole internal data structure is
# duplicated and replaced with a new one.
#
# @api private
class CopyOnWriteObserverSet < Synchronization::LockableObject
def initialize
super()
synchronize { ns_initialize }
end
# @!macro observable_add_observer
def add_observer(observer = nil, func = :update, &block)
if observer.nil? && block.nil?
raise ArgumentError, 'should pass observer as a first argument or block'
elsif observer && block
raise ArgumentError.new('cannot provide both an observer and a block')
end
if block
observer = block
func = :call
end
synchronize do
new_observers = @observers.dup
new_observers[observer] = func
@observers = new_observers
observer
end
end
# @!macro observable_delete_observer
def delete_observer(observer)
synchronize doView on GitHub (pinned to 0b88d5ff75)
Solutions
- Register only the object: `subject.add_observer(listener)` (callback defaults to :update) or `subject.add_observer(listener, :on_event)`.
- Or register only the block — it is stored as the observer and called via #call.
- If both behaviors are wanted, register them as two separate observers.
Example fix
// before
subject.add_observer(listener) { |e| on(e) }
// after — object form
subject.add_observer(listener, :on)
// or block form
subject.add_observer { |e| on(e) } Defensive patterns
Strategy: validation
Validate before calling
def register(subject, observer = nil, func = :update, &block) raise ArgumentError, 'observer OR block, not both' if observer && block subject.add_observer(observer, func, &block) end
Type guard
def valid_registration?(observer, block_given) observer.nil? != block_given end
Try / catch
begin
subject.add_observer(listener) { |e| on(e) }
rescue ArgumentError => e
raise unless e.message.include?('observer and a block')
subject.add_observer(listener, :on)
end Prevention
- Register exactly one callback form; if both behaviors are needed, register two observers.
- Check wrappers for stray blocks appended to add_observer calls.
- Object observers use the func method (default :update); block observers use #call.
When it happens
Trigger: `subject.add_observer(listener) { |v| handle(v) }`; `set.add_observer(obj, :on_event) { ... }` — any call supplying both the positional observer and a block.
Common situations: Switching subjects between observer-set implementations and carrying over a double registration; helpers that always attach a block while callers also pass an object.
Related errors
- cannot provide both an observer and a block
- cannot give arguments and a block
- cannot give arguments and a block
- should pass observer as a first argument or block
- should pass observer as a first argument or block
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/b22341f81a75c789.
Report an issue: GitHub.