ruby-concurrency/concurrent-ruby · error · NameError

identifier #{name} needs to be constant

Error message

identifier #{name} needs to be constant

What it means

When a String name is given to the struct factory, the library registers the generated class as a constant under the parent module via const_set. Ruby requires constant names to start with an uppercase letter; a lowercase name makes const_set fail with NameError, which the library rescues and re-raises as NameError("identifier x needs to be constant"). The name must be a valid constant identifier like 'Point', not 'point'.

Source

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

      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
        end
        clazz.class_exec(&block) unless block.nil?
        clazz.singleton_class.send :alias_method, :[], :new
        clazz
      end
    end
  end
end

View on GitHub (pinned to 0b88d5ff75)

Solutions

  1. Capitalize before passing: name ? name.to_s.split('_').map(&:capitalize).join : nil
  2. Or simpler: name&.capitalize when a single word
  3. Skip the name entirely (pass no String) and assign the returned class to your own constant

Example fix

# before
Concurrent::MutableStruct.new('line_item', :sku, :qty)

# after
Concurrent::MutableStruct.new('LineItem', :sku, :qty)

# or keep it anonymous
LineItem = Concurrent::MutableStruct.new(:sku, :qty)
Defensive patterns

Strategy: validation

Validate before calling

name = name.split('_').map(&:capitalize).join if name.is_a?(String)
Concurrent::MutableStruct.new(name, *members)

Type guard

def constant_like?(name)
  name.is_a?(String) && name.match?(/\A[A-Z][A-Za-z0-9_]*\z/)
end

Prevention

When it happens

Trigger: Concurrent::MutableStruct.new('point', :x, :y) (lowercase first letter); names built dynamically from snake_case strings, e.g. "#{type}_struct"; names containing characters illegal in constants (dashes, spaces).

Common situations: Generating struct classes from table names or config keys that arrive snake_case; porting code that used Object.const_set with the same raw string; DSLs that accept arbitrary user-supplied type names.

Related errors


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