CrackedRuby logo

CrackedRuby

require and load

Technical documentation covering Ruby's require and load methods for file loading, dependency management, and code organization.

Core Modules Kernel Module
3.1.7

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