ruby-concurrency/concurrent-ruby · error · IndexError

offset #{member} too large for struct(size:#{@values.length}

Error message

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

What it means

ns_get backs the reader side of all concurrent struct flavors (ImmutableStruct, MutableStruct, SettableStruct). Reading with an Integer index that is >= the number of members raises IndexError, mirroring core Struct's behavior. Valid indices are 0..length-1.

Source

Thrown at lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb:62

      # @!visibility private
      def ns_values_at(indexes)
        @values.values_at(*indexes)
      end

      # @!macro struct_to_h
      #
      # @!visibility private
      def ns_to_h
        length.times.reduce({}){|memo, i| memo[self.class::MEMBERS[i]] = @values[i]; memo}
      end

      # @!macro struct_get
      #
      # @!visibility private
      def ns_get(member)
        if member.is_a? Integer
          if member >= @values.length
            raise IndexError.new("offset #{member} too large for struct(size:#{@values.length})")
          end
          @values[member]
        else
          send(member)
        end
      rescue NoMethodError
        raise NameError.new("no member '#{member}' in struct")
      end

      # @!macro struct_equality
      #
      # @!visibility private
      def ns_equality(other)
        self.class == other.class && self.values == other.values
      end

      # @!macro struct_each
      #

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Bounds-check before indexed reads: index < s.class.members.size
  2. Iterate idiomatically via each/to_h/values instead of manual indexes
  3. Replace magic numeric indexes with named accessors (s.x) so removing a member becomes a NoMethodError at the right call site

Example fix

# before
Point = Concurrent::MutableStruct.new(:x, :y)
pt = Point.new(1, 2)
coord = pt[2] # IndexError

# after
coord = pt[1]                     # last valid index
coord = pt.to_h.values if idx >= 2 # or iterate
Defensive patterns

Strategy: validation

Validate before calling

if i.is_a?(Integer)
  raise IndexError, "#{i} out of range" unless (0...struct.class.members.size).cover?(i)
end
value = struct[i]

Type guard

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

Try / catch

begin
  v = struct[i]
rescue IndexError
  v = nil # or fetch from struct.to_h with a default
end

Prevention

When it happens

Trigger: s[3] on a 2-member struct; each_with_index loops with an off-by-one bound (index <= members.size); array-like access patterns assumed from Hash#[] (which returns nil for missing keys).

Common situations: Off-by-one loop bounds; struct definitions trimmed while index-based readers remain; treating the struct as an Array in generic traversal code.

Related errors


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