问题
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 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.
来源:https://stackoverflow.com/questions/57235261/why-does-kernelrequire-raise-a-loaderror-in-ruby