{"record":{"id":"87b9e1139c0e0d40","repo":"ruby-concurrency/concurrent-ruby","slug":"offset-member-too-large-for-struct-size-valu","errorCode":null,"errorMessage":"offset #{member} too large for struct(size:#{@values.length})","messagePattern":"offset #(.+?) too large for struct\\(size:#(.+?)\\)","errorType":"exception","errorClass":"IndexError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb","lineNumber":62,"sourceCode":"      # @!visibility private\n      def ns_values_at(indexes)\n        @values.values_at(*indexes)\n      end\n\n      # @!macro struct_to_h\n      #\n      # @!visibility private\n      def ns_to_h\n        length.times.reduce({}){|memo, i| memo[self.class::MEMBERS[i]] = @values[i]; memo}\n      end\n\n      # @!macro struct_get\n      #\n      # @!visibility private\n      def ns_get(member)\n        if member.is_a? Integer\n          if member >= @values.length\n            raise IndexError.new(\"offset #{member} too large for struct(size:#{@values.length})\")\n          end\n          @values[member]\n        else\n          send(member)\n        end\n      rescue NoMethodError\n        raise NameError.new(\"no member '#{member}' in struct\")\n      end\n\n      # @!macro struct_equality\n      #\n      # @!visibility private\n      def ns_equality(other)\n        self.class == other.class && self.values == other.values\n      end\n\n      # @!macro struct_each\n      #","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb#L44-L80","documentation":"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.","triggerScenarios":"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).","commonSituations":"Off-by-one loop bounds; struct definitions trimmed while index-based readers remain; treating the struct as an Array in generic traversal code.","solutions":["Bounds-check before indexed reads: index < s.class.members.size","Iterate idiomatically via each/to_h/values instead of manual indexes","Replace magic numeric indexes with named accessors (s.x) so removing a member becomes a NoMethodError at the right call site"],"exampleFix":"# before\nPoint = Concurrent::MutableStruct.new(:x, :y)\npt = Point.new(1, 2)\ncoord = pt[2] # IndexError\n\n# after\ncoord = pt[1]                     # last valid index\ncoord = pt.to_h.values if idx >= 2 # or iterate","handlingStrategy":"validation","validationCode":"if i.is_a?(Integer)\n  raise IndexError, \"#{i} out of range\" unless (0...struct.class.members.size).cover?(i)\nend\nvalue = struct[i]","typeGuard":"def valid_struct_index?(struct, i)\n  i.is_a?(Integer) && i >= 0 && i < struct.class.members.size\nend","tryCatchPattern":"begin\n  v = struct[i]\nrescue IndexError\n  v = nil # or fetch from struct.to_h with a default\nend","preventionTips":["Use each/to_h/values for traversal instead of manual indexes","Derive loop bounds from members.size, never hard-code them","Replace numeric indexes with named readers so mistakes fail loudly at the right site"],"tags":["ruby","struct","abstract-struct","index-error","bounds-check"],"backgroundTag":"struct-index-out-of-bounds","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}