Search code examples
rubyruby-c-extension

Ruby constants in singleton methods


I have a Ruby C extension; I call that class FooC defined in ext/foo/foo.c, in that file I have

void Init_foo(void)
{
  VALUE cFoo = rb_const_get(rb_cObject, rb_intern("FooC"));
  :
  rb_define_const(cFoo, "BAR", 7);
  :

That is included in the "Ruby part" of the extension in lib/foo.rb like

class FooC ; end

require 'foo/foo'

class Foo < FooC
  class << self
    def puts_bar
      puts BAR
    end
    :
  end
  :
end

Unexpectedly, I get an error

NameError: uninitialized constant #<Class:Foo>::BAR

I was expecting that Ruby would not find BAR in the Foo namespace, but would then search FooC where it would find it. Strangely, if I use Foo::BAR or self::BAR, the error goes away, and a similar usage in a Foo instance method gives me no errors at all.

Anyone have an idea as to why I need this seemingly redundant qualification of constants in singleton methods?

[edit]

Following the interesting answer below, I confirm that this qualification is not needed in a proper class method:

class Foo < FooC
  def self.puts_bar
    # no NameError here
    puts BAR
  end
  :
end

rather than a instance method of the eigenclass.


Solution

  • You are looking the constant up in the eigenclass of Foo, not in the Foo itself. Start with figuring out what’s what without any bindings.

    class FooC; end
    class Foo < FooC
      BAR = 42
      puts "Foo: #{self}, DEF: #{const_defined?(:BAR)}"
      class << self
        def puts_bar
          puts "Foo.class: #{self} DEF: #{self.class.const_defined?(:BAR)}"
          puts self.class.const_get(:BAR)
        end
      end
      def puts_bar
        puts "Foo.class: #{self} DEF: #{self.class.const_defined?(:BAR)}"
        puts self.class.const_get(:BAR)
      end
    end
    

    And see how it works:

    #  Foo: Foo, DEF: true
    
    [2] pry(main)> Foo.puts_bar
    #⇒ Foo.class: Foo DEF: false
    #   NameError: uninitialized constant Class::BAR
    #   from (pry):8:in `const_get'
    
    [3] pry(main)> Foo.new.puts_bar
    #⇒ Foo.class: #<Foo:0x0000556e174b91c0> DEF: true
    #   42
    

    Constants are made visible within instance functions by looking up the class’ constants. #<Class:Foo> in the error message you’ve got is explicitly pointing out to that.