ruby-concurrency/concurrent-ruby · error · NameError

no member '#{member}' in struct

Error message

no member '#{member}' in struct

What it means

When []= receives a non-Integer member it dispatches to the member's writer method via send("#{member}="). An undefined member produces NoMethodError, which the library rescues and re-raises as NameError("no member 'x' in struct") to mimic core Struct behavior. Only members declared at definition time exist; SettableStruct does not support dynamic fields.

Source

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

    #
    # @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)
      synchronize do
        super(original)
        ns_initialize_copy
      end
    end

    # @!macro struct_new
    def self.new(*args, &block)
      clazz_name = nil
      if args.length == 0
        raise ArgumentError.new('wrong number of arguments (0 for 1+)')
      elsif args.length > 0 && args.first.is_a?(String)

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Whitelist before writing: raise unless struct.class.members.include?(member.to_sym)
  2. Use Concurrent::MutableStruct/SettableStruct only for fixed schemas; switch to Hash for dynamic keys
  3. After renaming members, grep for old names in string/symbol form at assignment sites

Example fix

# before
Person = Concurrent::SettableStruct.new(:name)
p = Person.new
p[:age] = 30 # NameError

# after
allowed = Person.members
member = member.to_sym if allowed.include?(member.to_sym)
p[member] = value if member
Defensive patterns

Strategy: type-guard

Validate before calling

member = member.to_sym
raise NameError, "unknown member #{member}" unless struct.class.members.include?(member)
struct[member] = value

Type guard

def known_member?(struct_class, name)
  struct_class.members.include?(name.to_sym)
end

Try / catch

begin
  struct[name] = value
rescue NameError => e
  raise unless e.message.include?('no member')
  unknown << name # collect and report schema drift
end

Prevention

When it happens

Trigger: s[:age] = 25 or s.age = 25 when the struct was defined without :age; typos like s[:nmae] = x; forwarding user-supplied field names from params/JSON directly into []=.

Common situations: API payloads whose keys drift from the struct definition; renaming a member without updating callers; using a struct where a Hash (arbitrary keys) was actually needed.

Related errors


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