为什么 Kernel#require 在 Ruby 中引发 LoadError?
Why does Kernel#require raise a LoadError in Ruby?
嗨,多年来我一直想知道为什么你不能使用 Kernel#require
方法来加载宝石。
例如,这将起作用:
#!/usr/bin/ruby -w
require 'ruby2d' # => true
这里require的所有者是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 方法]
不知何故这不起作用:
#!/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)
这是怎么回事?
这里发生了一些事情,并且以有趣的方式进行交互,我们需要拆开它们才能了解正在发生的事情。
首先,require
是如何工作的。有一个包含目录列表的全局变量 $LOAD_PATH
。 require
工作的“原始”方式(即没有 Rubygems)是 Ruby 将简单地搜索此列表以查找所需文件,如果找到则加载它,否则会引发异常。
Rubygems 改变了这一点。当 Rubygems 被加载时它 replaces the built-in require
method with its own,首先对原始文件进行别名。这个新方法首先调用原始方法,如果没有找到所需的文件,那么它不会立即引发异常,而是搜索已安装的 gems,如果找到匹配的文件,则 gem 是已激活。这意味着(除其他外)gem 的 lib
目录被添加到 $LOAD_PATH
.
尽管 Rubygems 现在是 Ruby 的一部分并默认安装,但它仍然是一个单独的库并且原始代码仍然存在。 (您可以使用 --disable=gems
禁用加载 Rubygems)。
接下来,我们可以看看原来的require
方法是怎么定义的。是 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
.
上的单例方法(即 class 方法)
现在我们可以开始看看发生了什么。 Rubygems 代码仅替换 require
的包含版本。当您调用 Kernel.require
时,您会得到原始的 require
方法,它对 Rubygems.
一无所知
如果你运行
p Kernel.require 'ruby2d'
您将得到与您 运行 禁用 Rubygems (ruby --disable=gems p.rb
) 时相同的错误:
p require 'ruby2d'
在这两种情况下我得到:
Traceback (most recent call last):
1: from p.rb:1:in `<main>'
p.rb:1:in `require': cannot load such file -- ruby2d (LoadError)
这不同于如果我 运行 第二个例子 with Rubygems,在这种情况下我得到(因为我不没有安装 gem):
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
s,但是一个已经 Rubygems 而一个还没有。
也可以解释 Kernel.require
似乎有效的例子,因为在那些情况下文件已经被加载,而原始 require
代码只是看到一个已经加载的文件并且 returns 错误。 Kernel.require
也可以工作的另一个例子是
gem 'ruby2d'
Kernel.require 'ruby2d'
gem
方法会激活 gem,尽管它不会加载它。如上所述,这会将 gems lib 目录(包含作为 require 目标的文件)添加到 $LOAD_PATH
,因此原始 require
代码将找到它并加载它.
嗨,多年来我一直想知道为什么你不能使用 Kernel#require
方法来加载宝石。
例如,这将起作用:
#!/usr/bin/ruby -w
require 'ruby2d' # => true
这里require的所有者是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 方法]
不知何故这不起作用:
#!/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)
这是怎么回事?
这里发生了一些事情,并且以有趣的方式进行交互,我们需要拆开它们才能了解正在发生的事情。
首先,require
是如何工作的。有一个包含目录列表的全局变量 $LOAD_PATH
。 require
工作的“原始”方式(即没有 Rubygems)是 Ruby 将简单地搜索此列表以查找所需文件,如果找到则加载它,否则会引发异常。
Rubygems 改变了这一点。当 Rubygems 被加载时它 replaces the built-in require
method with its own,首先对原始文件进行别名。这个新方法首先调用原始方法,如果没有找到所需的文件,那么它不会立即引发异常,而是搜索已安装的 gems,如果找到匹配的文件,则 gem 是已激活。这意味着(除其他外)gem 的 lib
目录被添加到 $LOAD_PATH
.
尽管 Rubygems 现在是 Ruby 的一部分并默认安装,但它仍然是一个单独的库并且原始代码仍然存在。 (您可以使用 --disable=gems
禁用加载 Rubygems)。
接下来,我们可以看看原来的require
方法是怎么定义的。是 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
.
现在我们可以开始看看发生了什么。 Rubygems 代码仅替换 require
的包含版本。当您调用 Kernel.require
时,您会得到原始的 require
方法,它对 Rubygems.
如果你运行
p Kernel.require 'ruby2d'
您将得到与您 运行 禁用 Rubygems (ruby --disable=gems p.rb
) 时相同的错误:
p require 'ruby2d'
在这两种情况下我得到:
Traceback (most recent call last):
1: from p.rb:1:in `<main>'
p.rb:1:in `require': cannot load such file -- ruby2d (LoadError)
这不同于如果我 运行 第二个例子 with Rubygems,在这种情况下我得到(因为我不没有安装 gem):
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
s,但是一个已经 Rubygems 而一个还没有。
也可以解释 Kernel.require
似乎有效的例子,因为在那些情况下文件已经被加载,而原始 require
代码只是看到一个已经加载的文件并且 returns 错误。 Kernel.require
也可以工作的另一个例子是
gem 'ruby2d'
Kernel.require 'ruby2d'
gem
方法会激活 gem,尽管它不会加载它。如上所述,这会将 gems lib 目录(包含作为 require 目标的文件)添加到 $LOAD_PATH
,因此原始 require
代码将找到它并加载它.