为什么Kernel#require在Ruby中引发LoadError? [英] Why does Kernel#require raise a LoadError in Ruby?
问题描述
我多年来一直想知道为什么您不能使用Kernel#require
方法加载宝石.
Hi I have wondered for years why you can't use the Kernel#require
method for loading gems.
例如,这将起作用:
#!/usr/bin/ruby -w
require 'ruby2d' # => true
require的所有者是内核:
Here require's owner is Kernel:
p Object.method(:require).owner # => Kernel
p Kernel.method(:require).owner # => #<Class:Kernel>
但这可行:
p Object.send :require, 'ruby2d' # => true
p String.send :require, 'ruby2d' # => false
p Kernel.require 'ruby2d' # => false
或
gem 'ruby2d' # => true
p String.send :require, 'ruby2d' # => true
p Kernel.require 'ruby2d' # => false
[不好的主意,但是您可以在任何对象上发送require方法]
[bad idea, but you can send the require method on any Object]
以某种方式不起作用:
#!/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)
这是怎么回事?
推荐答案
这里发生了几件事,并且以有趣的方式进行交互,我们需要取消选择这些链接才能了解正在发生的事情.
There’s a couple of things going on here and interacting in interesting ways that we need to unpick to understand what’s happening.
首先,require
的工作方式.有一个包含目录列表的全局变量$LOAD_PATH
. require
的原始"工作方式(即没有Rubygems)是,Ruby会简单地在此列表中搜索所需的文件,如果找到该文件,则将其加载,否则将引发异常.
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改变了这一点.加载Rubygems时,它替换已构建的-in require
方法及其自己的,首先别名原始方法.此新方法首先调用原始方法,如果未找到所需文件,则它将立即搜索已安装的gem,而不是立即引发异常,并且如果找到匹配的文件,则该激活.这意味着(除其他事项外)宝石的lib
目录已添加到$LOAD_PATH
.
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
.
即使Rubygems现在是Ruby的一部分并默认安装,它仍然是一个单独的库,并且原始代码仍然存在. (您可以使用--disable=gems
禁用加载Rubygems.)
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
).
接下来,我们来看一下原始的require
方法是如何定义的.它是使用C函数rb_define_global_function
完成的完成.此函数依次调用rb_define_module_function
,然后该功能看起来像:
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);
}
如您所见,该方法最终被定义为两次,一次是作为私有方法(即包含在Object
中并且可以在任何地方使用的方法),一次是作为单例方法(即Kernel
上的类方法.
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
.
现在我们可以开始看看发生了什么. Rubygems代码仅替换require
的随附版本.调用Kernel.require
时,您会得到原始的require
方法,该方法对Rubygems一无所知.
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.
如果您运行
p Kernel.require 'ruby2d'
您将得到与在禁用Rubygems(ruby --disable=gems p.rb
)的情况下运行以下命令相同的错误:
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)
这与我使用Rubygems运行第二个示例 的情况不同,在这种情况下,我得到了(因为我没有安装gem):
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)
两者都是LoadError
,但是其中一个经历过Rubygems,另一个没有经历过.
Both LoadError
s, but one has gone through Rubygems and one hasn’t.
也可以解释Kernel.require
似乎起作用的示例,因为在这种情况下,文件已经被加载,并且原始的require
代码只是看到已经加载的文件并返回false. Kernel.require
也将起作用的另一个示例是
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'
gem
方法会激活gem,尽管它不会加载它.如上所述,这会将gems lib目录(包含require的目标文件)添加到$LOAD_PATH
,因此原始的require
代码将找到并加载它.
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.
这篇关于为什么Kernel#require在Ruby中引发LoadError?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!