ruby-concurrency/concurrent-ruby · error · IndexError

offset #{member} too large for struct(size:#{length})

Error message

offset #{member} too large for struct(size:#{length})

What it means

SettableStruct instances are populated once per member; []= with an Integer index is bounds-checked against the member count and raises IndexError when index >= length. The struct is defined via SettableStruct.new(:a, :b) and instances of that generated class accept assignment by index or by name. Members can only be set while still nil; re-setting raises Concurrent::ImmutabilityError.

Source

Thrown at lib/concurrent-ruby/concurrent/settable_struct.rb:79

    def each_pair(&block)
      return enum_for(:each_pair) unless block_given?
      synchronize { ns_each_pair(&block) }
    end

    # @!macro struct_select
    def select(&block)
      return enum_for(:select) unless block_given?
      synchronize { ns_select(&block) }
    end

    # @!macro struct_set
    #
    # @raise [Concurrent::ImmutabilityError] if the given member has already been set
    def []=(member, value)
      if member.is_a? Integer
        length = synchronize { @values.length }
        if member >= length
          raise IndexError.new("offset #{member} too large for struct(size:#{length})")
        end
        synchronize do
          unless @values[member].nil?
            raise Concurrent::ImmutabilityError.new('struct member has already been set')
          end
          @values[member] = value
        end
      else
        send("#{member}=", value)
      end
    rescue NoMethodError
      raise NameError.new("no member '#{member}' in struct")
    end

    private

    # @!visibility private
    def initialize_copy(original)

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Prefer named assignment: s.name = value instead of positional indexes
  2. Bounds-check dynamic indexes: raise if index >= s.class.members.size
  3. After changing a struct's member list, grep for numeric indexing into that struct and update the constants

Example fix

# before
Person = Concurrent::SettableStruct.new(:name, :age)
p = Person.new
p[2] = ' Berlin' # IndexError

# after
p[1] = 30          # or better, by name:
p.age = 30
Defensive patterns

Strategy: validation

Validate before calling

size = struct.class.members.size
raise IndexError, "index #{i} out of bounds" unless i.is_a?(Integer) && i < size
struct[i] = value

Type guard

def valid_struct_index?(struct, i)
  i.is_a?(Integer) && i >= 0 && i < struct.class.members.size
end

Try / catch

begin
  struct[i] = value
rescue IndexError
  logger.warn("dropping write to unknown slot #{i}")
end

Prevention

When it happens

Trigger: s = K.new; s[2] = value on a struct with 2 members (valid indices 0..1); loop code using a computed index that runs one past the end; indexing with a stale constant after members were removed from the definition.

Common situations: Editing a struct definition (removing a member) while callers still assign by old positions; metaprogramming that maps array positions to struct slots; copy-paste between structs of different arity.

Related errors


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