ruby-concurrency/concurrent-ruby · error · ArgumentError

cannot give arguments and a block

Error message

cannot give arguments and a block

What it means

The copy-on-write observer set's notify_to delivers notifications to a set of observers and accepts either a fixed argument list or a block that recomputes args per observer — passing both is ambiguous and raises ArgumentError. Unlike the copy-on-notify variant this method is private, so it is reached through subclasses of the set, custom Observable implementations, or explicit send(:notify_to, ...).

Source

Thrown at lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb:87

      #
      # @param [Object] args arguments to be passed to each observer
      # @return [CopyOnWriteObserverSet] self
      def notify_and_delete_observers(*args, &block)
        old = clear_observers_and_return_old
        notify_to(old, *args, &block)
        self
      end

      protected

      def ns_initialize
        @observers = {}
      end

      private

      def notify_to(observers, *args)
        raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
        observers.each do |observer, function|
          args = yield if block_given?
          observer.send(function, *args)
        end
      end

      def observers
        synchronize { @observers }
      end

      def observers=(new_set)
        synchronize { @observers = new_set }
      end

      def clear_observers_and_return_old
        synchronize do
          old_observers = @observers
          @observers = {}

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass either positional args or the block, never both: `notify_to(observers, :changed, data)` or `notify_to(observers) { args }`.
  2. In forwarding code, branch on block_given? before delegating.

Example fix

// before
set.send(:notify_to, observers, :changed, payload) { fresh_payload }

// after
set.send(:notify_to, observers, :changed, payload)
// or
set.send(:notify_to, observers) { fresh_payload }
Defensive patterns

Strategy: validation

Validate before calling

if block_given?
  set.send(:notify_to, observers) { yield }
else
  set.send(:notify_to, observers, *args)
end

Try / catch

begin
  set.send(:notify_to, observers, :changed, payload) { fresh }
rescue ArgumentError => e
  raise unless e.message == 'cannot give arguments and a block'
  set.send(:notify_to, observers, :changed, payload)
end

Prevention

When it happens

Trigger: A subclass of CopyOnWriteObserverSet (or Observable code reusing it) calling `notify_to(observers, :changed, data) { block }`; forwarding wrappers that pass both *args and &block into the private notify_to via send.

Common situations: Custom notification strategies subclassing one of the observer sets; refactoring notify plumbing from the copy-on-notify set into the copy-on-write set without dropping one of the two forms.

Related errors


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