ruby-concurrency/concurrent-ruby · error · ArgumentError

no block given

Error message

no block given

What it means

Channel#each is the enumeration loop that pops items from the channel until it is closed and drained. Unlike core Ruby enumerables, this implementation does not return a lazy Enumerator when called without a block - it raises ArgumentError immediately, because the block is the only way to consume each item.

Source

Thrown at lib/concurrent-ruby-edge/concurrent/channel.rb:194

      (item = do_poll) == Concurrent::NULL ? nil : item
    end

    def poll!
      item = do_poll
      raise Error if item == Concurrent::NULL
      item
    end

    def poll?
      if (item = do_poll) == Concurrent::NULL
        Concurrent::Maybe.nothing
      else
        Concurrent::Maybe.just(item)
      end
    end

    def each
      raise ArgumentError.new('no block given') unless block_given?
      loop do
        item, more = do_next
        if item != Concurrent::NULL
          yield(item)
        elsif !more
          break
        end
      end
    end

    class << self
      def timer(seconds)
        Channel.new(Buffer::Timer.new(seconds))
      end
      alias_method :after, :timer

      def ticker(interval)
        Channel.new(Buffer::Ticker.new(interval))

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Pass the consuming block: channel.each { |item| handle(item) }.
  2. If you need items as a collection, drain explicitly with take/pop in a loop (after closing the channel) instead of relying on Enumerator chaining.
  3. In wrappers that forward to each, check block_given? and raise a clearer error naming your API.

Example fix

# before
channel.each   # raises: no block given

# after
channel.each { |item| handle(item) }
Defensive patterns

Strategy: validation

Validate before calling

def drain(channel)
  raise ArgumentError, 'a consumer block is required' unless block_given?
  channel.each { |item| yield(item) }
end

Prevention

When it happens

Trigger: channel.each invoked with no block - a bare channel.each statement, or channel.each(&nil) through a helper whose block variable was never set.

Common situations: Refactoring each { ... } into chains like each_with_object or map (which call each without a consumer block of the right shape); passing a conditional block (cond && proc {...}) that evaluates to nil; expecting Hash#each-style Enumerator semantics such as channel.each.each_slice(10).

Related errors


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