ruby-concurrency/concurrent-ruby · error · ArgumentError

only one of block or value can be supplied

Error message

only one of block or value can be supplied

What it means

Inside an ErlangActor Environment, `on(matcher, value = nil, &block)` builds one receive rule: a matcher paired with either an inline reply value (returned when the matcher hits) or a handler block. `value || block` picks the handler, so supplying both a truthy positional value and a block is ambiguous and raises ArgumentError immediately. Note that `nil` as the value with a block is fine — only a truthy second argument plus a block conflicts.

Source

Thrown at lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb:758

      end

      def pid
        @Pid
      end

      def traps?
        @trap
      end

      def trap(value = true)
        old = @trap
        # noinspection RubySimplifyBooleanInspection
        @trap = !!value
        old
      end

      def on(matcher, value = nil, &block)
        raise ArgumentError, 'only one of block or value can be supplied' if block && value
        [matcher, value || block]
      end

      def receive(*rules, timeout: nil, timeout_value: nil, **options, &block)
        raise NotImplementedError
      end

      def link(pid)
        return true if pid == @Pid
        if @Linked.add? pid
          pid.tell Link.new(@Pid)
          if pid.terminated.resolved?
            # no race since it only could get NoActor
            if @trap
              tell Terminate.new pid, NoActor.new(pid)
            else
              @Linked.delete pid
              raise NoActor.new(pid)

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Delete the positional value and keep the block when the reply must be computed: `on(matcher) { |m| ... }`.
  2. Or delete the block and keep the constant reply: `on(matcher, :stop)`.
  3. In wrapper code, forward only the winner: `on(matcher, block ? nil : value, &block)`.
  4. Standardize your codebase on one style per rule to keep receive tables scannable.

Example fix

// before
receive on(Ping, :pong) { |msg| :pong }

// after
receive on(Ping, :pong)
// or
receive on(Ping) { |msg| :pong }
Defensive patterns

Strategy: validation

Validate before calling

def rule(matcher, value = nil, &block)
  raise ArgumentError, 'pass either a value or a block, not both' if value && block
  on(matcher, value, &block)
end

Try / catch

begin
  env.on(matcher, value, &blk)
rescue ArgumentError
  env.on(matcher, &blk) # prefer the block, drop the value
end

Prevention

When it happens

Trigger: `env.on(Ping, :pong) { |msg| :pong }` — both a constant reply symbol and a block. Wrappers that always capture a block via `&block` and also forward a caller-supplied value: `on(matcher, user_value, &user_block)`. Legal: `on(Ping, :pong)` or `on(Ping) { |msg| ... }` or `on(Ping, nil, &blk)`.

Common situations: Refactoring a value-style rule into a computed block (or vice versa) and leaving the old argument in place; DSLs around `receive` that blindly forward both forms; copy-pasted examples mixing the two styles.

Related errors


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