1

This is a theoretical question. I know that I can call a method by using ::. It is rarely used, but possible. For example:

user = User.new
user::name

The question is: How it is working under the hood? What are the implementation details allowing us to do user::name?

I found some places where this way of calling method is mentioned like:

but they do not provide the implementation details, which for me is interesting topic. I also tried to go through the Ruby source code, but for now without any success.

I also checked those threads:

They are explaining how :: works and differences between :: and .. And this is not what I search for.

I would appreciate any information that can help me to learn more about :: to understand it deeply. My question is about how the :: works inside, not how to use it. I would like to get to know implementation details for ::.

womanonrails
  • 380
  • 3
  • 7
  • Implementation details for which implementation? I can name more than a dozen different implementations of Ruby off the top of my head, including, but not limited to, Rubinius, XRuby, Ruby.NET, Ruby+OMR, Alumina, IoRuby (aka YARI), tinyrb, BlueRuby, RubyMotion, MacRuby, TruffleRuby, Topaz, Opal, Corundum, RubyGoLightly, Smalltalk.rb, RubyInRuby, MetaRuby, Cardinal, and many others. And which version? As long as it is compliant with the Ruby specification, there is no reason to assume that version x works the same as version y. Also, why would it work different than `.` "under the hood"? – Jörg W Mittag Aug 23 '23 at 10:26
  • Good point that in different implementations of Ruby `::` can works in different way. For the `.` I assumed that since `.` and `::` are not the same and they behave in different ways (in some cases), they have different implementation. – womanonrails Aug 23 '23 at 13:58

2 Answers2

2

One way to find out:

puts RubyVM::InstructionSequence.compile("M::M").disasm
puts

puts RubyVM::InstructionSequence.compile("M.m").disasm
puts
puts RubyVM::InstructionSequence.compile("M::m").disasm
# this one does a constant lookup only

== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,4)> (catch: false)
0000 opt_getconstant_path                   <ic:0 M::M>               (   1)[Li]
0002 leave


# these two are identical and do a method call

== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: false)
0000 opt_getconstant_path                   <ic:0 M>                  (   1)[Li]
0002 opt_send_without_block                 <calldata!mid:m, argc:0, ARGS_SIMPLE>
0004 leave

== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,4)> (catch: false)
0000 opt_getconstant_path                   <ic:0 M>                  (   1)[Li]
0002 opt_send_without_block                 <calldata!mid:m, argc:0, ARGS_SIMPLE>
0004 leave

https://rubyapi.org/3.2/o/rubyvm/instructionsequence


You can search for opt_getconstant_path and find where :: gets compiled:

https://github.com/ruby/ruby/blob/v3_2_2/compile.c#L9112

Given my zero c knowledge, ^ clearly shows if something then constant, else function call (because says so in the comments :).

Alex
  • 16,409
  • 6
  • 40
  • 56
-3

In Ruby, the double colons :: are used as a namespace resolution operator to access constants, classes, or modules within a module or class. When it comes to method calls, the double colons are used in the context of class or module methods, not instance methods.

Here's how the double colons work under the hood in case you use them for method calls:

Constant/Class/Module Lookup: When you use Module::Class or Module::CONSTANT, Ruby searches for the specified class, module, or constant within the given module's namespace. It searches first in the module itself, then looks up the ancestor chain.

Module/Class Method Calls: When you use Module::method or Class::method, you are calling a method that is defined on the module or class itself, not on instances of that module or class.

Under the hood, Ruby is looking up the method within the class/module's method table. This is essentially a hash-like structure that maps method names to their corresponding method implementations. The method lookup starts from the most specific class/module and goes up the ancestor chain if the method is not found in the current class/module.

This process is a fundamental part of Ruby's object-oriented nature and its inheritance and method resolution mechanisms.

Here's a simplified example to illustrate how it works:

module MyModule
   def self.my_method
      puts "This is a class/module method!"
   end
end

class MyClass
   def self.my_method
     puts "This is another class/module method!"
   end
end

MyModule::my_method  # Outputs: "This is a class/module method!"
MyClass::my_method   # Outputs: "This is another class/module method!"

In this example, both MyModule and MyClass have a class/module method named my_method. Using the double colons to call these methods directly accesses them from the corresponding class/module.

Remember that the double colons primarily pertain to constants, classes, modules, and their associated methods, not instance methods. For instance methods, you typically use dot notation on instances of classes.