ruby/ruby
Require, autoload, and the loader
load.c (~55 KB) implements Kernel#require, Kernel#load, Kernel#autoload, and the $LOAD_PATH ($:) machinery. The loader manages the cache of already-loaded files, enforces thread/Ractor safety during loading, and bridges to C extensions via dln.c.
Purpose
- Implement
require,require_relative,load, andautoload. - Maintain
$LOADED_FEATURES($") so each feature loads at most once. - Resolve a feature name to a file path, looking through
$LOAD_PATH. - Load Ruby source via the parser/compiler/VM, and load C extensions via
dlopen. - Be safe across threads, Ractors, and Fiber Schedulers.
Files
| File | Purpose |
|---|---|
load.c |
Core require/load/autoload. |
loadpath.c |
$LOAD_PATH setup and standard library path injection. |
dln.c |
Cross-platform dynamic library loader (dln_load, dln_find_exe). |
dln_find.c |
Path searching helper. |
dln.h |
Public interface. |
kernel.rb |
Ruby-level Kernel#require_relative etc. |
lib/bundled_gems.rb |
The list of bundled-gem requires that emit a warning when activated implicitly. |
$LOAD_PATH ordering
$LOAD_PATH is the array of directories searched on require "name". At startup it's populated by loadpath.c::ruby_init_loadpath:
1. directories from -I command-line flags
2. directories from RUBYLIB env var
3. site_ruby (system-wide additions): <prefix>/lib/ruby/site_ruby/<api-ver>
4. vendor_ruby: <prefix>/lib/ruby/vendor_ruby/<api-ver>
5. gem-installed default-gem paths
6. stdlib: <prefix>/lib/ruby/<api-ver>Order matters: the first matching file wins. Conflicting names between user code, gems, and stdlib are resolved this way.
How require works
graph LR
call["require 'foo'"] --> resolve[Resolve via $LOAD_PATH]
resolve --> seen{seen in $LOADED_FEATURES?}
seen -->|yes| ret[return false]
seen -->|no| lock[Acquire load lock for path]
lock --> kind{Ruby or .so?}
kind -->|.rb| parse[Parse + compile + execute]
kind -->|.so| dlopen[dlopen + Init_xxx]
parse --> add[Add path to $LOADED_FEATURES]
dlopen --> add
add --> ret2[return true]load.c::rb_require_internal is the entry point. Highlights:
- Both forms (
nameandname.rb/name.so) are accepted; the loader tries.rbfirst then.so. - The "feature name" stored in
$LOADED_FEATURESis the resolved absolute path. Sorequire 'foo/bar'andrequire './foo/bar.rb'(with./foo/bar.rbmatching) deduplicate correctly. - A per-feature lock prevents two threads from racing to load the same file. The second thread blocks until the first finishes; if the first thread is in the current Ractor, the lock is non-recursive.
Autoload
autoload(:Foo, "foo") registers a placeholder for Foo: the first reference to the constant triggers require "foo". Implementation in variable.c::autoload_state and load.c::rb_autoload_load.
autoload :HTTP, 'net/http'
HTTP # at this point, 'net/http' is required and HTTP must be definedAutoload is thread-safe: concurrent references on different threads block until the loading thread finishes.
autoload?(:Foo) returns the registered file (or nil).
Kernel#autoload_relative (added in Ruby 4.x — see NEWS.md) resolves the path relative to the calling file, mirroring require_relative.
Bundled-gem warnings
When Ruby 3.4+ is asked to require 'csv' (or another gem that has graduated from default to bundled status), lib/bundled_gems.rb emits a one-time warning suggesting the user add gem 'csv' to their Gemfile. The mechanism is a hook installed at boot time.
C extension loading
dln_load("foo.so") (in dln.c) opens the shared library and calls its Init_foo function. The Init_xxx symbol is the convention every CRuby C extension follows:
void
Init_mygem(void)
{
VALUE m = rb_define_module("MyGem");
rb_define_singleton_method(m, "hello", mygem_hello, 0);
}dln.c handles platform differences:
- POSIX:
dlopen+dlsym. - Windows:
LoadLibrary+GetProcAddress. - Static linking: a registry of
Init_*functions indmydln.canddmyext.c.
The static-linking path is used by builds like the WASM port (wasm/) where dynamic loading isn't available.
Compaction and the loader
The loader registers per-feature data with the GC. When the GC compacts, paths and feature names move; load.c::loaded_features_index_clear_i rebuilds the path-to-index hash after compaction.
$LOADED_FEATURES vs $LOAD_PATH
$LOAD_PATH($:) — directories searched.$LOADED_FEATURES($") — files already loaded (full paths or short names).- Both are arrays; standard array methods work, but mutating them during a
requireis undefined.
Ractor interaction
require runs only in the main Ractor by default. Calling it from another Ractor raises Ractor::IsolationError. The reason is that $LOAD_PATH and $LOADED_FEATURES are global state, plus loading code defines methods on shared classes (which is also forbidden from non-main Ractors).
Ractor.make_shareable doesn't help here — the restriction is structural.
Common pitfalls
- Modifying
$LOAD_PATHafter gems load: gems are activated based on$LOAD_PATHorder at boot. Late additions don't re-resolve old activations. - Cyclic requires: A requires B requires A. Ruby allows it, but the partially-loaded version is returned. Constants defined later may not exist yet — leading to
NameError. - Symlinks across
require_relative:require_relativeresolves the directory of the current file, which on symlinked layouts can surprise. require 'rubygems'is implicit: removed long ago; you don't need it.
Entry points for modification
- Loader bug:
load.c::rb_require_internaland the helperssearch_required/loaded_features_index_add. - New autoload variant: extend
variable.c::autoload_state_*and surface fromkernel.rb. - Path search order:
loadpath.c::ruby_init_loadpath_safebuilds the initial array. - C extension loading on a new platform: extend
dln.cwith the platform's dynamic-loader API.
See extensions.md for the C extension story and reference/configuration.md for $LOAD_PATH env vars.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.