Overview
Ruby provides two primary mechanisms for loading external files and code: require
and load
. Both methods execute Ruby code from external files, but they differ significantly in their caching behavior, scope handling, and intended use cases.
The require
method loads a file once and caches the result. Subsequent calls to require
with the same filename return immediately without re-executing the file. This behavior makes require
suitable for loading libraries, gems, and other code that should only execute once during a program's lifetime.
require 'json'
require 'json' # Second call does nothing, returns false
The load
method executes a file every time it's called, never caching results. This behavior makes load
appropriate for configuration files, templates, or any code that needs fresh execution on each call.
load 'config.rb'
load 'config.rb' # Executes config.rb again
Ruby maintains a global $LOAD_PATH
array (also accessible as $:
) that contains directories where Ruby searches for files. When you call require 'filename'
, Ruby searches through each directory in $LOAD_PATH
until it finds a matching file.
puts $LOAD_PATH
# => ["/usr/lib/ruby/3.0.0", "/usr/lib/ruby/gems/3.0.0/gems/...", ...]
$LOAD_PATH << './lib' # Add current lib directory to load path
Both methods can load .rb
Ruby files and compiled extensions (.so
, .dll
, .bundle
). Ruby determines the file type automatically and handles loading appropriately.
Basic Usage
The require
method accepts either relative or absolute file paths. For relative paths, Ruby searches through $LOAD_PATH
directories. The method returns true
when successfully loading a file for the first time, and false
when the file was already loaded.
# Load from standard library
require 'date'
require 'fileutils'
# Load relative to load path
require 'my_gem/core'
# Absolute path (not recommended for portability)
require '/home/user/project/lib/helper.rb'
When using relative paths with require
, omit the .rb
extension. Ruby automatically tries various extensions in a specific order: .rb
, .so
, .o
, .dll
, and others depending on the platform.
# These are equivalent
require 'my_library'
require 'my_library.rb'
# Ruby tries in order:
# my_library.rb
# my_library.so
# my_library.o (on some platforms)
The load
method requires an explicit file extension and path. Unlike require
, it doesn't search through $LOAD_PATH
unless you provide a relative path that exists in those directories.
# Explicit .rb extension required
load 'config.rb'
load './settings.rb'
load '/absolute/path/to/file.rb'
# This would cause an error
# load 'config' # LoadError: cannot load such file
You can modify $LOAD_PATH
to control where Ruby searches for files. This proves useful when organizing code into directories or when working with non-standard project structures.
# Add directories to load path
$LOAD_PATH.unshift('./lib')
$LOAD_PATH << File.expand_path('../vendor', __FILE__)
# Now require can find files in these directories
require 'custom_library' # Searches ./lib/custom_library.rb
require 'vendor_code' # Searches ../vendor/vendor_code.rb
Advanced Usage
Ruby tracks loaded files in the global $LOADED_FEATURES
array (also accessible as $"
). This array contains the absolute paths of all files loaded via require
. You can inspect this array to debug loading issues or prevent double-loading.
require 'json'
puts $LOADED_FEATURES.grep(/json/)
# => ["/usr/lib/ruby/3.0.0/json.rb", "/usr/lib/ruby/3.0.0/json/common.rb", ...]
# Check if a file is already loaded
if $LOADED_FEATURES.any? { |f| f.include?('my_library') }
puts "Library already loaded"
end
The load
method accepts an optional second parameter for anonymous module wrapping. When true
, Ruby executes the loaded file within an anonymous module, preventing constants and methods from polluting the global namespace.
# file: temp_config.rb
API_KEY = "secret123"
def helper_method
"temporary helper"
end
# Without anonymous module (default)
load 'temp_config.rb'
puts API_KEY # => "secret123"
# With anonymous module
load 'temp_config.rb', true
puts API_KEY # => NameError: uninitialized constant API_KEY
You can implement custom loading mechanisms by manipulating $LOAD_PATH
and using require
strategically. This approach enables plugin systems, dynamic library loading, and custom dependency resolution.
class LibraryLoader
def initialize(base_path)
@base_path = File.expand_path(base_path)
@loaded_libraries = {}
end
def load_library(name, version = 'latest')
return @loaded_libraries[name] if @loaded_libraries[name]
library_path = File.join(@base_path, name, version)
$LOAD_PATH.unshift(library_path) unless $LOAD_PATH.include?(library_path)
require File.join(library_path, "#{name}.rb")
@loaded_libraries[name] = true
rescue LoadError => e
raise "Failed to load library #{name} (#{version}): #{e.message}"
end
end
loader = LibraryLoader.new('./plugins')
loader.load_library('authentication', 'v2.1')
For complex projects, you can create autoloading systems that defer file loading until constants are first accessed. While Ruby on Rails provides comprehensive autoloading, you can implement basic versions for smaller projects.
module AutoLoader
@autoload_paths = {}
def self.register(const_name, file_path)
@autoload_paths[const_name] = file_path
end
def self.const_missing(name)
if file_path = @autoload_paths[name.to_s]
require file_path
const_get(name)
else
super
end
end
end
# Register autoloadable constants
AutoLoader.register('DatabaseConnection', 'lib/database_connection')
AutoLoader.register('EmailService', 'lib/services/email_service')
Error Handling & Debugging
The most common error when using require
and load
is LoadError
, which occurs when Ruby cannot find the specified file. This error provides limited information about why the file wasn't found, making debugging challenging.
begin
require 'nonexistent_library'
rescue LoadError => e
puts e.message # => cannot load such file -- nonexistent_library
puts e.path # => nonexistent_library
# Debug by checking load path
puts "Searched in:"
$LOAD_PATH.each { |path| puts " #{path}" }
end
When debugging LoadError
, examine the search paths and verify file permissions. Ruby searches directories in $LOAD_PATH
order, so earlier directories take precedence.
def debug_require(filename)
$LOAD_PATH.each_with_index do |path, index|
full_path = File.join(path, "#{filename}.rb")
if File.exist?(full_path)
puts "Found at position #{index}: #{full_path}"
puts "Readable: #{File.readable?(full_path)}"
return full_path
end
end
puts "File #{filename} not found in any load path"
nil
end
debug_require('missing_library')
Circular dependencies create complex loading scenarios that can cause LoadError
or infinite loops. Ruby detects some circular dependencies but not all patterns.
# file_a.rb
require_relative 'file_b'
class A
def use_b
B.new
end
end
# file_b.rb
require_relative 'file_a' # Circular dependency
class B
def use_a
A.new # This might fail depending on loading order
end
end
To resolve circular dependencies, restructure code to avoid circular references, use autoloading, or defer constant resolution until runtime.
# Defer constant resolution
class B
def use_a
Object.const_get('A').new # Resolves A at runtime
end
end
# Or restructure to eliminate circular dependency
# Move shared functionality to a third file
When loading files with syntax errors, Ruby raises SyntaxError
instead of LoadError
. This error provides line numbers and context for debugging.
begin
require 'broken_syntax'
rescue SyntaxError => e
puts e.message
# => /path/to/broken_syntax.rb:15: syntax error, unexpected end-of-input
puts "File: #{e.path}"
puts "Line: #{e.lineno}" if e.respond_to?(:lineno)
end
Performance & Memory
The require
method's caching behavior significantly impacts performance. Files loaded via require
execute only once, but Ruby still performs file system checks on subsequent require
calls to verify the file exists in $LOAD_PATH
.
require 'benchmark'
# First require: file execution + file system lookup
time_first = Benchmark.realtime { require 'json' }
# Subsequent requires: only file system lookup
time_second = Benchmark.realtime { require 'json' }
puts "First require: #{time_first * 1000}ms"
puts "Second require: #{time_second * 1000}ms"
# Second require is much faster but not zero cost
Long $LOAD_PATH
arrays increase lookup time for require
calls. Ruby searches directories sequentially until finding a match, so frequently used libraries should be placed earlier in the load path.
# Measure load path impact
require 'benchmark'
# Add many directories to load path
100.times { |i| $LOAD_PATH << "/fake/path/#{i}" }
time_long_path = Benchmark.realtime { require 'nonexistent_file' rescue nil }
# Clear and test with shorter path
$LOAD_PATH.clear
$LOAD_PATH.concat(['/usr/lib/ruby/3.0.0'])
time_short_path = Benchmark.realtime { require 'nonexistent_file' rescue nil }
puts "Long path search: #{time_long_path * 1000}ms"
puts "Short path search: #{time_short_path * 1000}ms"
Memory usage accumulates with each file loaded via require
. Loaded files remain in memory for the program's lifetime, as their code becomes part of the Ruby process. The load
method doesn't cache files but still consumes memory for constants and methods defined during execution.
def memory_usage
GC.start # Force garbage collection
`ps -o rss= -p #{Process.pid}`.to_i # RSS in KB on Unix systems
end
before_memory = memory_usage
require 'large_library'
after_memory = memory_usage
puts "Memory increase: #{after_memory - before_memory} KB"
For applications that load many files, consider lazy loading strategies that defer require
calls until necessary. This approach reduces startup time and memory usage for unused features.
class LazyLoader
def initialize
@loaded = {}
end
def load_feature(name, &block)
@loaded[name] ||= begin
block.call
true
end
end
end
loader = LazyLoader.new
# Define lazy loading blocks
loader.load_feature(:database) { require 'sequel'; require 'pg' }
loader.load_feature(:web) { require 'sinatra'; require 'rack' }
# Features load only when accessed
def use_database
loader.load_feature(:database)
# Database libraries now available
end
Common Pitfalls
One frequent mistake involves confusing require
and require_relative
behavior. The require
method searches $LOAD_PATH
, while require_relative
loads files relative to the current file's location.
# Directory structure:
# project/
# ├── main.rb
# └── lib/
# ├── helper.rb
# └── utils.rb
# In main.rb - WRONG
require 'lib/helper' # Searches $LOAD_PATH for 'lib/helper'
# In main.rb - CORRECT
require_relative 'lib/helper' # Loads ./lib/helper.rb
# In lib/helper.rb - WRONG
require_relative 'utils' # Error if utils.rb not in lib/
# In lib/helper.rb - CORRECT
require_relative 'utils' # Loads lib/utils.rb correctly
Another common issue occurs when modifying $LOAD_PATH
after libraries are already loaded. Changes to $LOAD_PATH
don't affect previously loaded files, and can cause confusion about why require
calls succeed or fail.
# Library loaded from standard location
require 'json'
# Later, modify load path
$LOAD_PATH.unshift('./custom_libs')
# This still loads standard library version, not custom version
require 'json' # Returns false, already loaded
# To load custom version, you'd need to:
# 1. Remove from $LOADED_FEATURES
# 2. Modify $LOAD_PATH before first require
The load
method's lack of caching can cause performance problems when used inappropriately. Loading large files repeatedly consumes CPU and memory resources.
# WRONG - loads file every iteration
1000.times do
load 'expensive_calculations.rb'
perform_calculation
end
# CORRECT - require once, call methods repeatedly
require_relative 'expensive_calculations'
1000.times do
perform_calculation
end
File extension assumptions can cause subtle bugs. While require
automatically tries different extensions, load
requires explicit extensions, and the order of extension checking can vary between platforms.
# File: mylib.rb and mylib.so both exist
require 'mylib' # Might load .rb or .so depending on platform
load 'mylib' # LoadError: cannot load such file
load 'mylib.rb' # Loads Ruby file specifically
Relative path confusion frequently causes LoadError
in different execution contexts. A file that loads successfully when run directly might fail when required from another location.
# In project/lib/database.rb
load '../config/settings.rb' # Works when run from lib/
# But fails when required from project root:
# ruby -r './lib/database' -e 'puts "loaded"'
# LoadError: cannot load such file -- ../config/settings.rb
# Solution: use absolute paths or File.expand_path
load File.expand_path('../config/settings.rb', __FILE__)
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
require(filename) |
filename (String) |
Boolean |
Loads file once, returns true on first load, false if already loaded |
require_relative(filename) |
filename (String) |
Boolean |
Loads file relative to current file location |
load(filename, wrap=false) |
filename (String), wrap (Boolean) |
true |
Executes file every time called, optional anonymous module wrapping |
Global Variables
Variable | Type | Description |
---|---|---|
$LOAD_PATH / $: |
Array | Directories Ruby searches for files during require |
$LOADED_FEATURES / $" |
Array | Absolute paths of all files loaded via require |
Common File Extensions
Extension | Platform | Description |
---|---|---|
.rb |
All | Ruby source files |
.so |
Unix/Linux | Shared object libraries |
.bundle |
macOS | Native extensions |
.dll |
Windows | Dynamic link libraries |
.o |
Some Unix | Object files |
Load Path Modification Methods
Method | Effect | Example |
---|---|---|
$LOAD_PATH << path |
Append to end | $LOAD_PATH << './lib' |
$LOAD_PATH.unshift(path) |
Prepend to beginning | $LOAD_PATH.unshift('./lib') |
$LOAD_PATH.concat(paths) |
Append multiple paths | $LOAD_PATH.concat(['./lib', './vendor']) |
$LOAD_PATH.clear |
Remove all paths | $LOAD_PATH.clear |
Exception Types
Exception | Cause | Typical Resolution |
---|---|---|
LoadError |
File not found in load path | Check file path and $LOAD_PATH contents |
SyntaxError |
Ruby syntax errors in loaded file | Fix syntax in target file |
NameError |
Circular dependency issues | Restructure dependencies or use autoloading |
Debugging Commands
# Check if file is already loaded
$LOADED_FEATURES.any? { |f| f.include?('filename') }
# Find where Ruby would load a file from
$LOAD_PATH.each { |path| puts Dir.glob(File.join(path, 'filename*')) }
# Display current load path
puts $LOAD_PATH
# Show all loaded features
puts $LOADED_FEATURES
# Get file location after loading
require 'json'
puts $LOADED_FEATURES.grep(/json\.rb$/).first
Performance Considerations
Scenario | Recommendation |
---|---|
Library loading | Use require for one-time loading and caching |
Configuration files | Use load for files that change during runtime |
Plugin systems | Modify $LOAD_PATH before requiring plugin files |
Large applications | Implement lazy loading for optional features |
Development mode | Use load for files that change frequently |
Production mode | Preload all dependencies with require |