ruby-concurrency/concurrent-ruby · error · ArgumentError

must set with either a value or a block

Error message

must set with either a value or a block

What it means

IVar#set (and the shared check_for_block_or_value! it calls, also used by Future#set and Promise#set) completes the IVar with either a value or a block — exactly one. It raises ArgumentError 'must set with either a value or a block' when you pass neither (bare ivar.set) or both (ivar.set(1) { ... }). Note that set(nil) is legal: nil is a real value, distinct from the internal NULL sentinel default.

Source

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

      self
    end

    # @!visibility private
    def notify_observers(value, reason)
      observers.notify_and_delete_observers{ [Time.now, value, reason] }
    end

    # @!visibility private
    def ns_complete_without_notification(success, value, reason)
      raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state
      set_state(success, value, reason)
      event.set
    end

    # @!visibility private
    def check_for_block_or_value!(block_given, value) # :nodoc:
      if (block_given && value != NULL) || (! block_given && value == NULL)
        raise ArgumentError.new('must set with either a value or a block')
      end
    end
  end
end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass exactly one: ivar.set(value) or ivar.set { compute }
  2. To complete with 'no result', pass an explicit nil: ivar.set(nil)
  3. Do not call set to reset or signal — IVars are write-once; create a new IVar instead

Example fix

# before
ivar.set
# or
ivar.set(1) { compute }

# after
ivar.set(1)
# or
ivar.set { compute }
Defensive patterns

Strategy: validation

Validate before calling

def complete_ivar(ivar, value = :__unset__, &block)
  has_value = (value != :__unset__)
  raise ArgumentError, 'set with exactly one of value or block' if has_value == !!block
  has_value ? ivar.set(value) : ivar.set(&block)
end

Try / catch

begin
  ivar.set(value)
rescue ArgumentError => e
  raise ArgumentError, 'set needs exactly a value or a block' if /value or a block/.match?(e.message)
  raise
rescue Concurrent::MultipleAssignmentError
  logger.warn('ivar already completed; ignoring duplicate set')
end

Prevention

When it happens

Trigger: Calling ivar.set with no arguments (often an attempt to 'flush' or 'reset' the IVar); passing both a value and a block; passing the value inside the block's closure and calling set() bare by mistake.

Common situations: Treating set like a no-arg completion signal; refactoring from value-style to block-style completion and leaving a bare call; confusion with #complete/#fulfill-style APIs of other promise libraries.

Related errors


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