ruby-concurrency/concurrent-ruby · error · ArgumentError

no block given

Error message

no block given

What it means

Inside a Channel.select block, sel.take(channel) registers a take clause whose block is invoked with the received value when the clause wins. Without a block the clause could never deliver its result, so registration raises ArgumentError.

Source

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

    class Selector

      def initialize
        @clauses = []
        @error_handler = nil
      end

      def case(channel, action, message = nil, &block)
        if [:take, :poll, :receive, :~].include?(action)
          take(channel, &block)
        elsif [:put, :offer, :send, :<<].include?(action)
          put(channel, message, &block)
        else
          raise ArgumentError.new('invalid action')
        end
      end

      def take(channel, &block)
        raise ArgumentError.new('no block given') unless block_given?
        @clauses << TakeClause.new(channel, block)
      end
      alias_method :receive, :take

      def put(channel, message, &block)
        @clauses << PutClause.new(channel, message, block)
      end
      alias_method :send, :put

      def after(seconds, &block)
        @clauses << AfterClause.new(seconds, block)
      end
      alias_method :timeout, :after

      def default(&block)
        raise ArgumentError.new('no block given') unless block_given?
        @clauses << DefaultClause.new(block)
      end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Always pass the result block: sel.take(ch) { |value| handle(value) }.
  2. When registering many takes, build the block in the loop so each clause has one: channels.each { |ch| sel.take(ch) { |v| handle(ch, v) } }.
  3. Validate handler presence before building the selector when wiring from config.

Example fix

# before
channels.each { |ch| sel.take(ch) }   # no block -> raises

# after
channels.each { |ch| sel.take(ch) { |value| handle(ch, value) } }
Defensive patterns

Strategy: validation

Validate before calling

channels.each do |ch|
  raise ArgumentError, 'handler block missing' unless handler
  sel.take(ch, &handler)
end

Prevention

When it happens

Trigger: sel.take(ch) with no block in a select DSL - typically programmatic registration (channels.each { |ch| sel.take(ch) }) that forgot the block, or sel.take(ch, &nil) via a nil handler variable.

Common situations: Registering clauses in a loop over a channel list and dropping the per-clause block; refactoring a literal block into a variable that ends up nil; building selectors from config where the handler key is missing.

Related errors


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