ruby-concurrency/concurrent-ruby · error · ArgumentError

no block given

Error message

no block given

What it means

Concurrent::Maybe.from is a factory that runs a block (with optional args) in a protected way: if it returns, you get a Just(value); if it raises, you get a Nothing wrapping the exception. The block is the whole point, so calling it without one raises ArgumentError 'no block given'.

Source

Thrown at lib/concurrent-ruby/concurrent/maybe.rb:138

    # Create a new `Maybe` using the given block.
    #
    # Runs the given block passing all function arguments to the block as block
    # arguments. If the block runs to completion without raising an exception
    # a new `Just` is created with the value set to the return value of the
    # block. If the block raises an exception a new `Nothing` is created with
    # the reason being set to the raised exception.
    #
    # @param [Array<Object>] args Zero or more arguments to pass to the block.
    # @yield The block from which to create a new `Maybe`.
    # @yieldparam [Array<Object>] args Zero or more block arguments passed as
    #   arguments to the function.
    #
    # @return [Maybe] The newly created object.
    #
    # @raise [ArgumentError] when no block given.
    def self.from(*args)
      raise ArgumentError.new('no block given') unless block_given?
      begin
        value = yield(*args)
        return new(value, NONE)
      rescue => ex
        return new(NONE, ex)
      end
    end

    # Create a new `Just` with the given value.
    #
    # @param [Object] value The value to set for the new `Maybe` object.
    #
    # @return [Maybe] The newly created object.
    def self.just(value)
      return new(value, NONE)
    end

    # Create a new `Nothing` with the given (optional) reason.

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. To wrap an existing value, use Concurrent::Maybe.new(value) or Concurrent::Maybe(value) — not from
  2. To run protected code, always give from a block: Maybe.from { risky } — extra args are passed to the block: Maybe.from(a, b) { |x, y| ... }
  3. If the operation is a stored callable, pass it as a block: Maybe.from(&callable)

Example fix

# before
result = Concurrent::Maybe.from(42)

# after
result = Concurrent::Maybe.new(42)      # wrap existing value
result = Concurrent::Maybe.from { 42 }   # run protected block
Defensive patterns

Strategy: validation

Validate before calling

raise ArgumentError, 'operation block required' unless block_given?
Concurrent::Maybe.from(*args) { |*a| yield(*a) }
# or, to wrap an existing value:
Concurrent::Maybe.new(value)

Type guard

->(obj) { obj.respond_to?(:call) } # then Maybe.from(&obj)

Try / catch

begin
  Concurrent::Maybe.from { risky }
rescue ArgumentError => e
  raise ArgumentError, 'Maybe.from needs a block' if /no block/.match?(e.message)
  raise
end

Prevention

When it happens

Trigger: Concurrent::Maybe.from(value) expecting Just(value) — wrong API; Maybe.from(*args) with the computation forgotten; storing the operation in a variable and passing it positionally.

Common situations: Confusing from with Maybe(value)/Maybe.new(value) which wrap an existing value; generic 'run op or capture error' helpers that take callables positionally; conditional code paths where the block is built only sometimes and ends up nil.

Related errors


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