Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Kernel#require raise a LoadError in Ruby?

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?

like image 209
S.Goswami Avatar asked Jul 27 '19 19:07

S.Goswami


People also ask

What is the purpose of the kernel?

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.

What is kernel and how it works?

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.

What is kernel example?

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.

What is a kernel Why is it often desired to have kernel as small as possible?

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.


1 Answers

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 LoadErrors, 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.

like image 136
matt Avatar answered Nov 06 '22 17:11

matt