Hi I have wondered for years why you can't use the Kernel#require
method for loading gems.
For example this will work:
#!/usr/bin/ruby -w
require 'ruby2d' # => true
Here require's owner is Kernel:
p Object.method(:require).owner # => Kernel
p Kernel.method(:require).owner # => #<Class:Kernel>
But this works:
p Object.send :require, 'ruby2d' # => true
p String.send :require, 'ruby2d' # => false
p Kernel.require 'ruby2d' # => false
or
gem 'ruby2d' # => true
p String.send :require, 'ruby2d' # => true
p Kernel.require 'ruby2d' # => false
[bad idea, but you can send the require method on any Object]
Somehow this doesn't work:
#!/usr/bin/ruby -w
p Kernel.require 'ruby2d'
Traceback (most recent call last):
1: from p.rb:2:in `<main>'
p.rb:2:in `require': cannot load such file -- ruby2d (LoadError)
What's going on here?
It is the core that provides basic services for all other parts of the OS. It is the main layer between the OS and underlying computer hardware, and it helps with tasks such as process and memory management, file systems, device control and networking.
The kernel is a core component of an operating system and serves as the main interface between the computer's physical hardware and the processes running on it. The kernel enables multiple applications to share hardware resources by providing access to CPU, memory, disk I/O, and networking.
The definition of a kernel is a grain or seed, or the most important part of something. An example of a kernel is one uncooked piece of corn. An example of a kernel is the core of ones religious beliefs. The central or most important part; the core.
It is the part of the operating system that loads first, and it remains in main memory. Because it stays in memory, it is important for the kernel to be as small as possible while still providing all the essential services required by other parts of the operating system and applications.
There’s a couple of things going on here and interacting in interesting ways that we need to unpick to understand what’s happening.
First, how require
works. There is a global variable $LOAD_PATH
that contains a list of directories. The “original” way require
worked (that is, without Rubygems), is Ruby will simply search this list for the required file and if it is found load it, otherwise it will raise an exception.
Rubygems changes this. When Rubygems is loaded it replaces the built-in require
method with its own, aliasing the original first. This new method firsts calls the original, and if the required file is not found then instead of raising the exception immediately it will search the installed gems, and if a matching file is found then that gem is activated. This means (amongst other things) that the gem’s lib
dir is added to the $LOAD_PATH
.
Even though Rubygems is now part of Ruby and installed by default, it is still a separate library and the original code is still present. (You can disable loading Rubygems with --disable=gems
).
Next, we can look at how the original require
method is defined. It is done with the C function rb_define_global_function
. This function in turn calls rb_define_module_function
, and that function looks like:
void
rb_define_module_function(VALUE module, const char *name, VALUE (*func)(ANYARGS), int argc)
{
rb_define_private_method(module, name, func, argc);
rb_define_singleton_method(module, name, func, argc);
}
As you can see, the method ends up being defined twice, once as a private method (that is the one included into Object
and available everywhere), and once as a singleton method (that is, a class method) on Kernel
.
Now we can start to see what’s happening. The Rubygems code only replaces the included version of require
. When you call Kernel.require
you get the original require
method that doesn’t know anything about Rubygems.
If you run
p Kernel.require 'ruby2d'
you will get the same error as if you ran the following with Rubygems disabled (ruby --disable=gems p.rb
):
p require 'ruby2d'
In both cases I get:
Traceback (most recent call last):
1: from p.rb:1:in `<main>'
p.rb:1:in `require': cannot load such file -- ruby2d (LoadError)
This differs from if I run the second example with Rubygems, in which case I get (since I don’t have the gem installed):
Traceback (most recent call last):
2: from p.rb:1:in `<main>'
1: from /Users/matt/.rubies/ruby-2.6.1/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Users/matt/.rubies/ruby-2.6.1/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- ruby2d (LoadError)
Both LoadError
s, but one has gone through Rubygems and one hasn’t.
The examples where Kernel.require
seem to work can also be explained, since in those case the file has already been loaded, and the original require
codes simply sees an already loaded file and returns false. Another example where Kernel.require
will also work would be
gem 'ruby2d'
Kernel.require 'ruby2d'
The gem
method activates the gem, although it doesn’t load it. As described above this adds the gems lib dir (containing the file that is the target of the require) to the $LOAD_PATH
, and so the original require
code will find it and load it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With