ruby-concurrency/concurrent-ruby · error · ArgumentError
struct size differs
Error message
struct size differs
What it means
The generated struct class's ns_initialize accepts at most as many positional values as there are members; a longer argument list raises ArgumentError('struct size differs'). Fewer values are padded with nil, so only the upper bound is enforced. This is the concurrent structs' equivalent of core Struct's arity check.
Source
Thrown at lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb:146
# @!visibility private
def pr_underscore(clazz)
word = clazz.to_s.dup # dup string to workaround JRuby 9.2.0.0 bug https://github.com/jruby/jruby/issues/5229
word.gsub!(/::/, '/')
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
word.downcase!
word
end
# @!visibility private
def self.define_struct_class(parent, base, name, members, &block)
clazz = Class.new(base || Object) do
include parent
self.const_set(:MEMBERS, members.collect{|member| member.to_s.to_sym}.freeze)
def ns_initialize(*values)
raise ArgumentError.new('struct size differs') if values.length > length
@values = values.fill(nil, values.length..length-1)
end
end
unless name.nil?
begin
parent.send :remove_const, name if parent.const_defined?(name, false)
parent.const_set(name, clazz)
clazz
rescue NameError
raise NameError.new("identifier #{name} needs to be constant")
end
end
members.each_with_index do |member, index|
clazz.send :remove_method, member if clazz.instance_methods(false).include? member
clazz.send(:define_method, member) do
@values[index]
end
endView on GitHub (pinned to 0b88d5ff75)
Solutions
- Validate before splatting: raise ArgumentError, "expected <= #{K.members.size} values" if arr.length > K.members.size
- If truncation is acceptable by design, slice explicitly: K.new(*arr.first(K.members.size))
- After changing a member list, update every construction site of that struct class
Example fix
# before Point = Concurrent::MutableStruct.new(:x, :y) pt = Point.new(*csv_row) # raises when csv_row has 3+ columns # after raise ArgumentError, 'row too long' if csv_row.length > Point.members.size pt = Point.new(*csv_row.first(Point.members.size))
Defensive patterns
Strategy: validation
Validate before calling
max = Klass.members.size
raise ArgumentError, "#{values.length} values for #{max} members" if values.length > max
Klass.new(*values) Try / catch
begin Klass.new(*values) rescue ArgumentError => e raise unless e.message == 'struct size differs' Klass.new(*values.first(Klass.members.size)) # explicit truncation policy end
Prevention
- Validate splat length against members.size before constructing
- Update all construction sites when editing a member list
- Add an arity spec for each generated struct class
When it happens
Trigger: Point.new(1, 2, 3) for Point defined with 2 members; K.new(*arr) where arr outgrew the member list after a data change; passing a defaulted splat whose length varies per caller.
Common situations: A struct definition is trimmed (member removed) while some constructors still pass the old arity; splatting external data (CSV rows, JSON arrays) without length validation; merging two structs' constructors during a refactor.
Related errors
- wrong number of arguments (0 for 1+)
- offset #{member} too large for struct(size:#{@values.length}
- no member '#{member}' in struct
- identifier #{name} needs to be constant
- no block given
AI-assisted analysis of ruby-concurrency/concurrent-ruby@0b88d5ff75 (2026-08-21).
Data as JSON: /api/errors/19cf90a200844b20.
Report an issue: GitHub.