在Ruby中需要文件时,技术上会发生什么?

如果我有一个名为app.rb的文件需要另一个名为foo.rb文件,Ruby会做些什么来使foo.rb定义的常量在foo.rb可用?

来自Yukihiro Matsumoto的“The Ruby Programming Language”:

加载了load或require的文件在新的顶级作用域中执行,该作用域与调用load或require的作用域不同。 加载的文件可以查看在加载时已定义的所有全局变量和常量,但它无法访问从中启动加载的本地作用域。

我很难想象这个。


例如:

foo.rb

 class Foo def hello puts "Hello, world!" end end 

app.rb

 require_relative "foo" foo_obj = Foo.new foo_obj.hello # => "Hello, world!" p Object.constants.grep /^Foo/ # => [:Foo] 

Ruby如何将Foo常量添加到Object类中?

好吧,非常直接地,它在当前$ LOAD_PATH的所有目录中查找作为参数给出的路径。 如果它在$ LOAD_PATH中的一个基本路径中找到具有该部分路径的文件,则会加载它。 它还标记为已加载 – require将永远不会加载相同的文件两次,如果您需要相同的文件两次,第二次基本上是无操作。

关于本地范围,它非常简单:

  foo = "x" require 'some_file' 

那里所需的some_file(无论是加载require还是load )都无法访问该局部变量’foo’。 它不仅仅是代码在加载/需要时被粘贴。 相反,它被加载到它自己的本地范围,与加载/需要它的上下文不共享本地变量。

同样,在require之后,在所需的some_file中设置的局部变量也不可见。 同样,这不仅仅是代码粘贴在那一点上。 代码已加载,但它不会在加载/需要时与上下文共享任何本地变量访问。

您可能会或可能不会期望它。 就是这样,这就是它的意思。

但这只适用于局部变量。 在所需的some_file之外定义的常量仍然可用; 在所需的some_file中定义的常量在加载后仍可用于其他内容。 回想一下,ruby类实际上是通过常量访问的(SomeClass是一个常量,指向实际的类)。 所以这就是为什么some_file中定义的常量可用于任何其他代码,在需要或加载之后。

我不确定如何回答“它是如何做到这一点的”,除了查看C实现(这也超出了我)。 它只是…将文件加载到当前的ruby环境中。 这才是最有可能的。 但它加载它就好像它在自己的局部变量范围内,这就是为什么它不共享局部变量。

这是Ruby用来要求文件的底层C代码

https://github.com/ruby/ruby/blob/trunk/load.c#L959

 int rb_require_internal(VALUE fname, int safe) { volatile int result = -1; rb_thread_t *th = GET_THREAD(); volatile VALUE errinfo = th->errinfo; int state; struct { int safe; } volatile saved; char *volatile ftptr = 0; if (RUBY_DTRACE_REQUIRE_ENTRY_ENABLED()) { RUBY_DTRACE_REQUIRE_ENTRY(StringValuePtr(fname), rb_sourcefile(), rb_sourceline()); } TH_PUSH_TAG(th); saved.safe = rb_safe_level(); if ((state = EXEC_TAG()) == 0) { VALUE path; long handle; int found; rb_set_safe_level_force(safe); FilePathValue(fname); rb_set_safe_level_force(0); if (RUBY_DTRACE_FIND_REQUIRE_ENTRY_ENABLED()) { RUBY_DTRACE_FIND_REQUIRE_ENTRY(StringValuePtr(fname), rb_sourcefile(), rb_sourceline()); } path = rb_str_encode_ospath(fname); found = search_required(path, &path, safe); if (RUBY_DTRACE_FIND_REQUIRE_RETURN_ENABLED()) { RUBY_DTRACE_FIND_REQUIRE_RETURN(StringValuePtr(fname), rb_sourcefile(), rb_sourceline()); } if (found) { if (!path || !(ftptr = load_lock(RSTRING_PTR(path)))) { result = 0; } else if (!*ftptr) { rb_provide_feature(path); result = TAG_RETURN; } else { switch (found) { case 'r': state = rb_load_internal0(th, path, 0); break; case 's': handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext, path, 0, path); rb_ary_push(ruby_dln_librefs, LONG2NUM(handle)); break; } if (!state) { rb_provide_feature(path); result = TAG_RETURN; } } } } TH_POP_TAG(); load_unlock(ftptr, !state); rb_set_safe_level_force(saved.safe); if (state) { /* never TAG_RETURN */ return state; } th->errinfo = errinfo; if (RUBY_DTRACE_REQUIRE_RETURN_ENABLED()) { RUBY_DTRACE_REQUIRE_RETURN(StringValuePtr(fname), rb_sourcefile(), rb_sourceline()); } return result; } 

用他的所有方法创建一个新的Foo对象。 使用foo_obj.hello您将从类Foo调用方法hello

你现在有关于OOP的事吗?