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
CopyOnNotifyObserverSet is the observer registry used by Concurrent::Observable subjects. add_observer accepts either an observer object (with an optional callback method name, default :update) or a block, but not both — passing both is ambiguous about what should be notified, so it raises ArgumentError before anything is registered.
Source
Thrown at lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb:24
# A thread safe observer set implemented using copy-on-read approach:
# observers are added and removed from a thread safe collection; every time
# a notification is required the internal data structure is copied to
# prevent concurrency issues
#
# @api private
class CopyOnNotifyObserverSet < 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
@observers[observer] = func
observer
end
end
# @!macro observable_delete_observer
def delete_observer(observer)
synchronize do
@observers.delete(observer)
observerView on GitHub (pinned to 0b88d5ff75)
Solutions
- Remove the block when registering an object: `subject.add_observer(listener)` — the callback defaults to listener.update.
- Or name the object's callback method: `subject.add_observer(listener, :on_change)`.
- Or drop the object and keep only the block — the block itself becomes the observer and is invoked via #call.
Example fix
// before
chat.add_observer(logger) { |msg| log(msg) }
// after — object form
chat.add_observer(logger, :log)
// or block form
chat.add_observer { |msg| log(msg) } 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 # exactly one form supplied end
Try / catch
begin
subject.add_observer(listener) { |m| log(m) }
rescue ArgumentError => e
raise unless e.message.include?('observer and a block')
subject.add_observer(listener, :log)
end Prevention
- Pick one callback style per registration: object (with optional method symbol) or block, never both.
- Centralize registrations in a helper that enforces the either/or contract.
- Remember block observers are invoked via #call; object observers via the func argument (default :update).
When it happens
Trigger: `subject.add_observer(listener) { |value| handle(value) }`; `observer_set.add_observer(obj, :on_change) { ... }` — any call supplying both the positional observer and a block.
Common situations: Refactoring from block callbacks to object callbacks (or vice versa) and leaving the other form behind; adding a block 'for documentation' while also registering an object; wrapper methods that always append a block.
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/88d698a50e8f2bf7.
Report an issue: GitHub.