Why does Kernel#require raise a LoadError in Ruby?

谁说我不能喝 提交于 2020-04-10 17:41:45

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/57235261/why-does-kernelrequire-raise-a-loaderror-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!