为什么Kernel#require在Ruby中引发LoadError? [英] Why does Kernel#require raise a LoadError in Ruby?

查看:156
本文介绍了为什么Kernel#require在Ruby中引发LoadError?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我多年来一直想知道为什么您不能使用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 LoadErrors, 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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆