Variables, Constants and Scope
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Ruby 4.0 (4.0.7, checked on 2026-10-18). It covers the five kinds of names a Ruby program can bind a value to: local variables, instance variables, class variables, global variables and constants. For each it explains where the name is visible, what an unset name evaluates to, how to inspect it at run time, and which mistakes show up in real code. The running scenario is the shelf library-catalogue gem used throughout this section. Features newer than Ruby 3.0 carry their version in the text, and the version table near the end collects them.
Every example was syntax-checked with ruby -wc, run with Ruby 4.0.7, and linted with RuboCop 1.92 using its default
cops and TargetRubyVersion: 4.0. The output under each example is the real output of that run. A # rubocop:disable
comment marks the few places where an example breaks a cop on purpose because the cop is what the page is explaining.
Error messages use the Ruby 3.4 format ('name' with straight quotes and for main or for an instance of X).
The neighbouring pages build on this one:
Assignment and Multiple Assignment covers the
= operator itself, Blocks, Procs and Lambdas covers
closures in depth, and
Modules and Mixins covers what a module contributes to a lookup
chain.
The five kinds of names at a glance
A name’s first characters decide its kind, and its kind decides everything else: how far it is visible, what it holds before the first assignment, and how it can be listed.
| Kind | Looks like | Visible in | Unset value | Listing and access |
|---|---|---|---|---|
Local variable |
|
the current scope only: a method body, a class body, a file, or a block and the scopes around it |
a method call; a |
|
Instance variable |
|
every method of one object |
|
|
Class variable |
|
a class, its subclasses and their instances |
|
|
Global variable |
|
everywhere in the process |
|
|
Constant |
|
the lexical scopes around the reference, then the ancestors of the innermost one |
|
|
A bare lower-case name is either a local variable or a method call, and the next section explains how Ruby decides. Names that begin with an upper-case letter are constants:
class and module names are ordinary constants that happen to hold a Class or Module.
Source: Assignment
Local variables
A local variable name starts with a lower-case letter, an underscore or a non-ASCII character, and continues with
letters, digits and underscores. By convention it is snake_case. A local variable is created by assigning to it; there
is no declaration, no let, and no type. It stays visible for the rest of its scope and nothing outside that scope can
see it.
The parse-time rule
Ruby decides whether a bare lower-case word is a variable or a method call when it parses the file, not when it runs the code. The first time the parser sees an assignment to a name, every later occurrence of that name in the same scope is a local-variable read, whether or not the assignment ever executes. Before that point, the word is a method call with no arguments and no receiver.
# frozen_string_literal: true
# A bare name is a method call until the parser has seen an assignment to it.
def title = 'from the method'
puts title # the method
on_loan = false
title = 'from the local' if on_loan # never assigned, but the parser saw it
p title # the local variable, which is nil
title = 'now assigned'
puts title
puts self.title # rubocop:disable Style/RedundantSelf
p local_variables
from the method
nil
now assigned
from the method
[:on_loan, :title]
Three consequences follow from the output:
-
title = '...' if on_loannever ran its assignment, yettitleis now a local variable holdingnil. The methodtitleis shadowed for the rest of the scope. Call it with empty parentheses (title()) or with an explicit receiver (self.title) to reach the method again. -
A modifier condition is evaluated first, but it is written second.
p copies if (copies = 3).positive?fails with aNameErrorbecause the parser reachesp copiesbefore it has seen the assignment (thenon_gates.rbexample later on this page prints the real message). -
The classic trap
big_calculation = big_calculation()is legal: the right-hand side is a method call because of the parentheses, and from then on the name is a local variable.
x ||= 10 is the same rule used on purpose: the parser sees the assignment, so x is a local variable (with value
nil) by the time ||= reads it.
Scope gates
A scope gate is a construct that starts a new local-variable scope with no access to the enclosing one. Ruby has
three keyword gates (def, class and module), plus the top level of each file. Everything else, blocks included,
leaves the surrounding variables visible.
Constructs that open a new, isolated scope (a gate):
-
def: the method body sees none of the variables around thedef; -
class,moduleandclass << obj: the body starts a fresh scope; -
the top level of each file, including files loaded with
requireandload.
Constructs that do not isolate anything:
-
if,unless,case,while,until,for,begin,rescueandensureopen no scope at all; -
a block (
{ ... }ordo ... end), a lambda written with->, and the blocks given todefine_method,Class.new,Module.new,Struct.newandData.defineopen a nested scope: they can read and write the outer variables, and their own variables die with them; -
a string passed to
eval,instance_evalorclass_evalis a nested scope too: it sees the caller’s locals, and the variables it creates stay private to it.
A gate cuts the scope off completely, and a block is the way to cross a gate on purpose.
# frozen_string_literal: true
limit = 14
# def, class and module open a new scope: nothing outside is visible inside.
def days_allowed
limit
rescue NameError => e
e.message
end
puts days_allowed
# A block is a closure: it sees, and can change, the surrounding locals.
renewals = 0
3.times { renewals += 1 }
p renewals
# define_method takes a block, so it can capture a local from the class body.
class Reader
max_books = 3
define_method(:max_books) { max_books }
end
puts Reader.new.max_books
puts limit
undefined local variable or method 'limit' for main
3
3
14
Source: Assignment: Local Variable Scope
The def in days_allowed cannot see limit, which belongs to the top-level scope, so the NameError message
reports undefined local variable or method 'limit' for main. The block passed to 3.times is a closure: it reads and
writes renewals itself, and define_method takes a block, so the method it creates can read max_books from the
class body, which a def max_books could not.
The if, while and begin family never opens a scope, so a variable first assigned inside them is visible after
them. This is different from JavaScript’s let and from most C-family languages.
# frozen_string_literal: true
# if, unless, while, case and begin do not open a scope.
if ARGV.empty?
mode = :interactive
prompt = '> '
end
p mode, prompt
while (attempt ||= 0) < 2
attempt += 1
last_try = attempt
end
p last_try
# The modifier condition runs first, so the parser has not yet seen the assignment.
begin
p copies if (copies = 3).positive? # rubocop:disable Lint/UselessAssignment
rescue NameError => e
puts e.message
end
# A name with no assignment seen yet is a method call; ||= makes it a local.
count ||= 10
p count
# Struct.new and Data.define blocks do not open a constant scope.
Point = Struct.new(:x, :y) do
ORIGIN_X = 0 # rubocop:disable Lint/ConstantDefinitionInBlock
end
p Point.const_defined?(:ORIGIN_X, false)
p Object.const_defined?(:ORIGIN_X)
:interactive
"> "
2
undefined local variable or method 'copies' for main
10
false
true
Source: Assignment: Local Variable Scope
The last two lines show a related surprise: a constant assigned inside the block of Struct.new or Data.define is
defined in the enclosing lexical scope, here Object, and not on the new class. The constants section returns to
that.
Blocks, closures and shadowing
A block sees the variables of the scope it was written in, and those variables live as long as any block still refers to them. Variables first assigned inside the block do not survive it.
# frozen_string_literal: true
# Variables first assigned inside a block disappear when it ends.
[1, 2].each { |n| puts n * 2 }
p defined?(n)
# for does not open a scope, so its variable leaks.
for i in 1..3 do end # rubocop:disable Style/For
p i
# A block-local variable (after the semicolon) shadows an outer one on purpose.
total = 100
[1, 2, 3].each do |n; total|
total = n * 10
puts total
end
p total
# A plain block parameter with the same name also shadows the outer variable.
name = :outer
[:inner].each { |name| puts name }
p name
# Implicit parameters: it (3.4) and _1 to _9 (2.7).
tens = [1, 2, 3].map { it * 10 }
sums = [[1, 2], [3, 4]].map { _1 + _2 }
p tens, sums
2
4
nil
3
10
20
30
100
inner
:outer
[10, 20, 30]
[3, 7]
Source: Assignment: Local Variable Scope
-
defined?(n)isnilafter the first loop because the block parameternexists only inside the block. -
for i in 1..3does not open a scope.iis still readable afterwards and holds3. This is the main reason idiomatic Ruby preferseach; RuboCop’sStyle/Forflags it. -
A block-local variable, declared after a semicolon in the parameter list (
|n; total|), is a fresh variable that shadows the outertotaland leaves it untouched. Use it when a block needs a scratch variable and the same name already exists outside. -
A plain block parameter with the name of an outer variable also shadows it, and Ruby prints no warning for it any more (the old "shadowing outer local variable" warning was removed in 2.6). The outer
nameis unchanged afterwards, and the block’snameis a different variable. -
it(Ruby 3.4) and_1to_9(Ruby 2.7) are implicit block parameters.itis the name of the only parameter;_1and_2are the first and second parameters of a block that takes several. Assigning to a numbered parameter is aSyntaxErrorsince 3.0, and a block cannot mix numbered parameters with an ordinary parameter list. Ruby 4.0 stopsBinding#local_variablesfrom listing implicit parameters;Binding#implicit_parameterslists them instead.
See Blocks, Procs and Lambdas for the it and
numbered-parameter rules in full.
Flat scopes and closures
Because blocks cross scope gates, Ruby code that needs to hand a local variable to a method body uses a flat scope:
it replaces def with define_method, and class Name with Class.new do ... end. The block closes over the
surrounding locals, and the closure holds the variable, not a copy of its value.
# frozen_string_literal: true
prefix = '978'
# Class.new, Module.new and Data.define take blocks, so they can close over locals
# (the "flat scope" technique). A def inside would open a new scope and lose them.
Catalogued = Class.new do
define_method(:catalogue_prefix) { prefix }
def broken_prefix
prefix
rescue NameError => e
e.message.lines.first.chomp
end
end
item = Catalogued.new
p item.catalogue_prefix
p item.broken_prefix
prefix = '979' # the closure shares the variable, it does not copy it
p item.catalogue_prefix
# A lambda keeps its variables alive after the defining scope has ended.
def counter
count = 0
-> { count += 1 }
end
next_id = counter
next_id.call
p next_id.call
p next_id.binding.local_variables
"978"
"undefined local variable or method 'prefix' for an instance of Catalogued"
"979"
2
[:count]
Source: Module#define_method
broken_prefix is written with def, so it opens a new scope; prefix there is a method call, and since the class
has no method of that name the error message names the receiver as an instance of Catalogued. After prefix = '979'
the define_method body returns '979': the block shares the variable, so a later assignment is visible inside it.
The lambda returned by counter keeps its own count alive after counter has returned, which is the usual way to
build private mutable state without an object.
Unused variables and the underscore
Under -w, Ruby warns when a local variable is assigned and never read. The message names the variable, for example
warning: assigned but unused variable - copies. A leading underscore (_copies, or just _) silences the warning,
and the convention tells readers the value is deliberately ignored. A name that starts with an underscore is an ordinary
variable otherwise: it can still be read, and it still appears in local_variables.
Inspecting scope: local_variables, Binding and eval
Kernel#local_variables lists the names visible in the current scope, including names the parser has seen but the
program has not yet reached. Kernel#binding captures the current scope as a Binding object, which can read, write
and evaluate code in it.
# frozen_string_literal: true
# Binding exposes the locals of a scope as data.
def describe_scope(isbn)
_copies = 2
binding.local_variables.to_h { |name| [name, binding.local_variable_get(name)] }
end
p describe_scope('9780596516178')
# local_variable_set on a name the parser has not seen lives only in the binding.
scope = binding
scope.local_variable_set(:late, 1)
p scope.local_variable_get(:late)
p scope.local_variable_defined?(:late)
p defined?(late)
p scope.eval('late')
{isbn: "9780596516178", _copies: 2}
1
true
nil
1
Source: Binding#local_variable_set
-
binding.local_variableslists names;local_variable_getreads a value, andlocal_variable_setwrites one. -
local_variable_seton a name the parser has not seen creates the variable only inside thatBinding. The scope itself still treatslateas a method call, sodefined?(late)isnil; code evaluated through the binding (scope.eval('late')) does see it. -
evalwith a string is a nested scope of its own: assignments inside the string are not visible to the code around it, and twoevalcalls in the same scope do not see each other’s new variables. Theevaldocumentation in the Ruby guide has a worked example. Prefer aBindingor a block toevalfor anything that is not a REPL or a template engine; see Hooks, eval and DSLs. -
The top-level binding is available as the constant
TOPLEVEL_BINDING.
Source: Binding and Kernel#local_variables
Instance variables
An instance variable begins with a single @ and belongs to one object. Any method of that object reads and writes
it, including methods inherited from a superclass: there is no separate namespace per class, so a subclass and its
superclass see the same @name. No other object sees it. Unlike Java or C#, Ruby has no declaration: the variable
comes into existence the first time something assigns it, and different instances of one class may end up with
different sets.
# frozen_string_literal: true
# A library member who borrows titles.
class Reader
attr_reader :name
def initialize(name)
@name = name
end
def borrow(title)
@borrowed ||= []
@borrowed << title
self
end
def borrowed? = !@borrowed.nil?
end
ana = Reader.new('Ana')
p ana.instance_variables
p ana.borrowed? # reading an unset ivar gives nil, with no warning
p ana.instance_variable_get(:@borrowed)
ana.borrow('Dune').borrow('Emma')
p ana.instance_variables
p ana.instance_variable_get(:@borrowed)
p ana.instance_variable_defined?(:@name)
ana.instance_variable_set(:@vip, true) # any object accepts any ivar
p ana.instance_variables
p ana.remove_instance_variable(:@vip)
p ana.instance_variables
begin
ana.instance_variable_get('borrowed')
rescue NameError => e
puts e.message
end
p self
p @top_level_ivar
[:@name]
false
nil
[:@name, :@borrowed]
["Dune", "Emma"]
true
[:@name, :@borrowed, :@vip]
true
[:@name, :@borrowed]
'borrowed' is not allowed as an instance variable name
main
nil
Source: Object#instance_variable_get
Points worth knowing:
-
An unset instance variable reads as
nil. Before Ruby 3.0 that read printed a warning under-w; the warning was removed in 3.0, so code that relies on@x ||= []or on@x.nil?is warning-free. It also means a typo such as@nmaeis silentlynil: tests catch it, the interpreter does not. -
instance_variable_get(:@name),instance_variable_set,instance_variable_defined?andremove_instance_variableare the reflective API. The name must start with@: the string'borrowed'raisesNameErrorwith'borrowed' is not allowed as an instance variable name. -
The default
inspect(and sop) prints every instance variable. Ruby 4.0 lets a class narrow that output by defining a privateinstance_variables_to_inspectmethod that returns an array of names (see the example below). -
Instance variables are private to the object: the only way another object reads them is through a method.
attr_reader,attr_writerandattr_accessorgenerate those methods. See Classes and Objects and Visibility and Encapsulation. -
Everything is an object, so
selfat the top level (main) can have instance variables, and so can a class or a module.@top_level_ivarabove isnilbecause nothing assigned it. These are the "class-level instance variables" discussed in the next section. -
Since Ruby 3.2 the VM represents the instance-variable layout of an object as a shape, which makes ivar access faster. Objects whose variables are assigned in a consistent order share a shape, so assign every variable in
initialize, always in the same order, instead of adding new ones from random methods.
# frozen_string_literal: true
# A reader with a private token that should stay out of inspect output.
class Account
def initialize(name, token)
@name = name
@token = token
end
private
def instance_variables_to_inspect = [:@name]
end
puts Account.new('ana', 'demo-token').inspect.sub(/0x\h+/, '0x0')
#<Account:0x0 @name="ana">
Source: Object#inspect
An object that has been frozen (freeze) refuses new assignments to its instance variables with a FrozenError; see
Object Lifecycle, Freezing and Copying.
The setter pitfall
Inside a method, count = 5 always creates a local variable, even when the class has a count= writer. To call the
writer, write self.count = 5. RuboCop’s Style/RedundantSelf leaves such a self. alone for that reason.
# frozen_string_literal: true
# Setters need an explicit receiver: a bare assignment creates a local variable.
class Counter
attr_accessor :count
def initialize = @count = 0
def wrong
count = 5
[count, @count]
end
def right
self.count = 5
[count, @count]
end
end
counter = Counter.new
p counter.wrong
p counter.right
[5, 0]
[5, 5]
Source: Assignment: Assignment Methods
wrong returns [5, 0]: the local variable is 5 and the instance variable never changed. right goes through the
attr_accessor setter. The same applies to compound assignment: pass:c[self.count += 1] is correct and pass:c[count += 1] creates a
new local.
Source: Assignment: Instance Variables
Class variables
A class variable begins with @@. It is shared by the class that defines it, by every subclass and by every instance of
any of them. Reading one that has not been set raises NameError (unlike instance variables, which are nil).
The example also sets up the preferred alternative: a class-level instance variable, written @count inside the class
body. A class is an object, so it has its own instance variables, and the subclass Annex has its own @count.
# frozen_string_literal: true
# A branch library; @@opened is shared, @count is per class.
class Branch
@@opened = 0 # rubocop:disable Style/ClassVars
@count = 0
class << self
attr_accessor :count
end
def self.opened = @@opened
def initialize
@@opened += 1 # rubocop:disable Style/ClassVars
self.class.count += 1
end
end
# A smaller branch with its own counter.
class Annex < Branch
@count = 0
end
Branch.new
Annex.new
Annex.new
p Branch.opened, Annex.opened
p Branch.count, Annex.count
p Branch.class_variables, Annex.class_variables
p Annex.class_variable_get(:@@opened)
p Annex.class_variable_defined?(:@@missing)
p Branch.instance_variables
begin
Branch.class_variable_get(:@@missing)
rescue NameError => e
puts e.message
end
3
3
1
2
[:@@opened]
[:@@opened]
3
false
[:@count]
uninitialized class variable @@missing in Branch
Source: Assignment: Class Variables
Reading the output:
-
@@openedis one variable:Branch.openedandAnnex.openedboth report 3 after oneBranchand twoAnnexinstances were created. -
@countis two variables:Branch.countis 1 andAnnex.countis 2, because each class object stores its own andpass:c[self.class.count += 1]updates the class of the instance being created. -
class_variableslists names including inherited ones.Branch.instance_variableslists the class-level instance variable[:@count]. -
Annexneeded its own@count = 0: a subclass does not inherit class-level instance variables. When subclasses should start from the parent’s value, copy it in theinheritedhook; see Hooks, eval and DSLs and Singleton Classes and Class-Level Behavior.
Class variable @@x |
Class-level instance variable @x in the class body |
|
|---|---|---|
Shared with subclasses |
yes, one variable for the whole tree |
no, each class object has its own |
Read from instance methods |
directly ( |
through a reader on the class ( |
Unset value |
|
|
RuboCop |
|
no complaint |
The advice in most style guides is to prefer the class-level instance variable. A class variable is shared mutable state across an inheritance tree, which makes tests order-dependent and means that a library’s subclass can change the behaviour of its superclass.
Errors specific to class variables
Two rules were tightened in Ruby 3.0:
# frozen_string_literal: true
# A library whose subclass defines the same class variable.
class Library
end
# A branch of the library.
class Branch < Library
@@seats = 10 # rubocop:disable Style/ClassVars
def self.seats = @@seats
end
# Reopening the superclass and defining the same name overtakes the subclass.
class Library
@@seats = 99 # rubocop:disable Style/ClassVars
end
begin
Branch.seats
rescue RuntimeError => e
puts "#{e.class}: #{e.message}"
end
RuntimeError: class variable @@seats of Branch is overtaken by Library
Source: Assignment: Class Variables
If a class and an ancestor both define the same class variable, the one in the subclass is overtaken when the
ancestor is (re)defined. Before 3.0 this only printed a warning under -w; now any access raises a RuntimeError.
# frozen_string_literal: true
# Class variables cannot be used at the top level (RuntimeError since 3.0).
begin
@@shelf_count = 1 # rubocop:disable Style/ClassVars
rescue RuntimeError => e
puts "#{e.class}: #{e.message}"
end
RuntimeError: class variable access from toplevel
Source: Assignment: Class Variables
Accessing a class variable from the top level (outside any class or module body) is a RuntimeError for the same
reason: the variable would be defined on Object and shared with every class in the program.
Class variables are looked up through the lexical class (the innermost class or module keyword in the source), not
through the receiver. Methods defined with define_method in a block, and code in class_eval blocks, therefore see
the variables of the class where the block was written. class_variable_get and class_variable_set are the
reflective way to reach a specific class’s variable.
Source: Assignment: Class Variables
Global variables
A global variable begins with $ and is visible everywhere in the process. An unset global reads as nil, and under
-w Ruby prints a warning that the global was not initialized:
# frozen_string_literal: true
p $never_set # rubocop:disable Style/GlobalVars
uninitialized_global.rb:3: warning: global variable '$never_set' not initialized
nil
Source: Assignment: Global Variables
(This one file is run as ruby -w.) defined?($never_set) avoids the warning, and global_variables lists every name.
User-defined globals are rare in well-structured code and RuboCop’s Style/GlobalVars discourages them. They are not
thread-safe, they make tests depend on each other, and any gem can overwrite them. The next example defines some on
purpose to show the machinery.
# frozen_string_literal: true
require 'stringio'
# Style/GlobalVars discourages defining globals; this page shows them on purpose.
# rubocop:disable Style/GlobalVars
# Globals are visible everywhere.
$shelf_log_level = :info
p $shelf_log_level
p global_variables.include?(:$shelf_log_level)
p defined?($never_set)
# alias gives a global a second name that always agrees with the first.
alias $level $shelf_log_level
$level = :debug
p $shelf_log_level
# trace_var runs a block each time the global is assigned.
trace_var(:$shelf_log_level) { |value| puts "log level is now #{value}" }
$shelf_log_level = :warn
untrace_var(:$shelf_log_level)
# rubocop:enable Style/GlobalVars
# $stdout accepts any object that responds to write.
captured = StringIO.new
$stdout = captured
puts 'hidden'
$stdout = STDOUT
p captured.string
begin
$stdout = Object.new
rescue TypeError => e
puts e.message
end
:info
true
nil
:debug
log level is now warn
"hidden\n"
$stdout must have write method, Object given
Source: Kernel#trace_var
-
alias $level $shelf_log_levelmakes two names for one variable. It is a language keyword, not a method, and it works only for globals. -
trace_var(:$name) { |value| ... }runs the block each time the global is assigned;untrace_varremoves it. -
The stream variables
$stdout,$stderrand$stdinaccept any object that responds to the right method (writefor the output ones). Reassigning$stdoutis how libraries capture output in tests, and the constantSTDOUTkeeps pointing at the original stream so it can be restored.$>is another name for$stdout. Assigning an object that has nowriteraisesTypeErrorwith$stdout must have write method, Object given. -
Many specials cannot be assigned. Assigning
$PID(the same variable as$$) raisesNameError($PID is a read-only variable), and$1 = 2is aSyntaxError(Can't set variable $1); the table below says which is which.
Pre-defined global variables
Ruby defines a fixed set of special globals. Some are only names for something else, some are filled in by the
interpreter, and some change the behaviour of core methods when assigned. The table groups them as the official guide
does. Variables with an English name become available under that long name after require 'English'.
| Variable | English name |
Contains | Writable |
|---|---|---|---|
|
|
the exception being handled, or |
no ( |
|
|
|
only while an exception is being handled |
|
|
the last |
yes |
|
|
the matched substring |
no ( |
|
|
the text before the match |
no ( |
|
|
the text after the match |
no ( |
|
|
the last group matched |
no ( |
|
none |
the nth group of the last match |
no ( |
|
|
the input record separator, newline by default |
yes |
|
|
the output record separator, |
yes |
|
none |
the current standard streams |
yes |
|
|
|
no ( |
|
|
the same variable as |
yes (needs |
|
|
the line number of the last read |
yes (an |
|
|
the last line read by |
yes |
|
|
the script name, which can be reassigned |
yes (a |
|
|
the |
no ( |
|
|
the process id |
no ( |
|
|
the |
no ( |
|
|
the directories searched by |
no (mutate the array) |
|
|
the files already loaded |
no (mutate the array) |
|
none |
|
no ( |
|
none |
|
yes |
|
none |
|
yes |
|
|
the default |
yes (deprecated) |
|
|
the default |
yes (deprecated) |
|
none |
state set by the command-line options |
|
Source: Pre-Defined Global Variables
Exceptions, match data and the English module
$! and $@ are set while an exception is being handled and are reset when the rescue clause ends. The match
variables $~, $&, $1 and the others belong to the current method frame and thread: a regular-expression match
in a called method does not clobber the caller’s $1, and another thread has its own. That is also why they exist at all
in a language that prefers explicit objects; Regexp.last_match and MatchData are the explicit forms, and RuboCop’s
Style/PerlBackrefs suggests them. The English library (a default gem you load with require 'English') gives every
cryptic name a readable alias.
# frozen_string_literal: true
require 'English'
# $! and $@ are set while an exception is handled and reset afterwards.
begin
raise ArgumentError, 'bad ISBN'
rescue ArgumentError
p $ERROR_INFO
p $ERROR_POSITION == $ERROR_INFO.backtrace
end
p $ERROR_INFO
# The match globals belong to the current method frame and thread.
# Style/PerlBackrefs prefers Regexp.last_match; the globals are shown on purpose.
# rubocop:disable Style/PerlBackrefs
if 'ISBN 9780596516178' =~ /(\d{3})(\d+)/
p $LAST_MATCH_INFO[0], $LAST_MATCH_INFO.pre_match
p $PREMATCH, $POSTMATCH, $LAST_PAREN_MATCH
p $1, $2
end
def match_in_another_frame = 'X-1' =~ /(\d)/
match_in_another_frame
p $1
# rubocop:enable Style/PerlBackrefs
p $PROGRAM_NAME == $0 # rubocop:disable Style/SpecialGlobalVars
p $PID == Process.pid
p __FILE__ == $PROGRAM_NAME
p $LOAD_PATH.equal?($:) # rubocop:disable Style/SpecialGlobalVars
p $ARGV.equal?(ARGV)
begin
eval('$PID = 1', binding, __FILE__, __LINE__)
rescue NameError => e
puts "#{e.class}: #{e.message}"
end
#<ArgumentError: bad ISBN>
true
nil
"9780596516178"
"ISBN "
"ISBN "
""
"0596516178"
"978"
"0596516178"
"978"
true
true
true
true
true
NameError: $PID is a read-only variable
Source: Pre-Defined Global Variables
-
$ERROR_POSITION == $ERROR_INFO.backtraceistrue:$@is just the backtrace of$!. -
After the
beginblock,$ERROR_INFOisnilagain. -
$1after callingmatch_in_another_frameis still"978": the match in the other method did not change this frame’s match data. -
$PROGRAM_NAMEand$0are the same variable, so__FILE__ == $PROGRAM_NAMEis the usual "was this file run directly" test. Assigning to it changes the process title thatpsshows on most platforms. -
$LOAD_PATH.equal?($:)istrue: the short form is an alias, as$>is for$stdout.
See Regular Expressions and
Exceptions and Error Handling for the objects behind
$~ and $!.
Source: English
Warnings, $VERBOSE and the deprecated separators
$VERBOSE has three levels. false (the default) prints ordinary warnings; true (ruby -w, -W2 or -v) adds
the extra warnings; nil (-W0) suppresses every warning, including those raised by Kernel#warn.
# frozen_string_literal: true
# Warnings go through Kernel#warn; $VERBOSE = nil silences them all.
def old_api
warn 'old_api is deprecated', uplevel: 1
end
old_api
$VERBOSE = nil
old_api
warn 'also silent'
$VERBOSE = false
warn 'silent no longer'
verbose_levels.rb:8: warning: old_api is deprecated
silent no longer
Source: Kernel#warn
The first call to old_api prints its warn message with the caller’s location (uplevel: 1). Once $VERBOSE is nil,
neither old_api nor warn prints anything. Setting it back to false re-enables output. Assign $VERBOSE = nil in
a narrow, restored scope only: it also hides the "already initialized constant" warning and warnings from your
dependencies.
Deprecation warnings (category :deprecated) are off by default since Ruby 2.7.2 and are turned on by -w,
-W:deprecated or Warning[:deprecated] = true. Two old record-separator globals belong to that category:
# frozen_string_literal: true
# The old separator globals still work, but setting them is deprecated.
$, = ','
$; = ' '
p 'a b'.split
puts [1, 2].join
deprecated_separators.rb:4: warning: non-nil '$,' is deprecated
deprecated_separators.rb:5: warning: non-nil '$;' is deprecated
deprecated_separators.rb:6: warning: $; is set to non-nil value
["a", "b"]
deprecated_separators.rb:7: warning: $, is set to non-nil value
1,2
Source: Pre-Defined Global Variables
The output is for ruby -W:deprecated deprecated_separators.rb. Setting $, or $; to anything but nil is
deprecated since Ruby 2.7, and while set it changes the behaviour of every Array#join and String#split call in the
process, so the "default separators" are best left alone and passed explicitly (join(','), split(' ')). The input and
output record separators $/ and $\ carry a similar deprecation note for non-nil assignment other than by the
command line.
Pre-defined constants
Some constants are always defined. They hold the standard streams, the environment, and information about the running interpreter.
| Constant | Holds |
|---|---|
|
the original standard streams (the |
|
the environment variables (see |
|
the command-line arguments, and the concatenated contents of the files they name (or standard input) |
|
the |
|
version details of this interpreter |
|
the implementation, platform and banner |
|
a |
|
a module that groups the |
# frozen_string_literal: true
p RUBY_VERSION, RUBY_ENGINE
p ARGV
p ENV.fetch('SHELF_ENV', 'development')
p TOPLEVEL_BINDING.local_variable_defined?(:titles)
p DATA.class
titles = DATA.each_line(chomp: true).to_a
p titles
p TOPLEVEL_BINDING.local_variable_get(:titles)
__END__
Dune
Emma
"4.0.7"
"ruby"
[]
"development"
true
File
["Dune", "Emma"]
["Dune", "Emma"]
Source: Pre-Defined Global Constants
TOPLEVEL_BINDING.local_variable_defined?(:titles) is already true on its first use because of the parse-time rule
described earlier. ENV.fetch('SHELF_ENV', 'development') returns the default when the variable is unset. The ENV
and ARGV details live on
Ruby Command Line and Environment.
Thread-local and fiber-local state
When the real need is "state that belongs to one request, thread or fiber", reach for the storage Ruby provides for that instead of a global.
# frozen_string_literal: true
# Per-thread and per-fiber state, instead of a plain global.
Thread.current[:request_id] = 'r-1' # fiber-local, despite the name
Thread.current.thread_variable_set(:user, 'ana') # thread-local
Fiber.new do
p Thread.current[:request_id]
p Thread.current.thread_variable_get(:user)
end.resume
Thread.new do
p Thread.current[:request_id]
p Thread.current.thread_variable_get(:user)
end.join
p Thread.current[:request_id]
nil
"ana"
nil
nil
"r-1"
Source: Thread#thread_variable_set
Despite the name, Thread.current[:key] is fiber-local: the new Fiber and the new Thread both see nil. The
value set with thread_variable_set is truly thread-local: the fiber (which runs on the same thread) sees it and the
new thread does not. The last line shows that the main fiber still has its own 'r-1'. See Threads and the GVL and
Fibers and the Fiber Scheduler.
Source: Thread
Constants
A constant is a name that begins with an upper-case letter, usually written SCREAMING_SNAKE_CASE for values and
CamelCase for classes and modules. Constants live inside a module (the top level is Object) and are looked up by
rules that differ from the ones for variables. This section covers assigning and reassigning them, what a constant
protects (very little), and how a reference is resolved.
Assigning, listing and reassigning
# frozen_string_literal: true
# Lending rules for the shelf catalogue.
module Policy
MAX_LOANS = 5
MAX_LOANS = 7
end
p Policy::MAX_LOANS
# Lending rules, reopened.
module Policy
p constants.sort
p const_get(:MAX_LOANS)
p const_defined?(:LOST_FEE)
p const_source_location(:MAX_LOANS)
end
Policy.const_set(:LOST_FEE, 20)
p Policy::LOST_FEE
p Policy.constants.sort
p Policy.send(:remove_const, :LOST_FEE)
p Policy.const_defined?(:LOST_FEE)
begin
Policy::LOST_FEE
rescue NameError => e
puts e.message
p e.name, e.receiver
end
begin
Policy.const_get(:lost_fee)
rescue NameError => e
puts e.message
end
constants_api.rb:6: warning: already initialized constant Policy::MAX_LOANS
constants_api.rb:5: warning: previous definition of MAX_LOANS was here
7
[:MAX_LOANS]
7
false
["constants_api.rb", 6]
20
[:LOST_FEE, :MAX_LOANS]
20
false
uninitialized constant Policy::LOST_FEE
:LOST_FEE
Policy
wrong constant name lost_fee
Source: Module#const_get
-
Assigning a constant a second time is allowed. Ruby prints two warnings on standard error,
already initialized constant Policy::MAX_LOANSand thenprevious definition of MAX_LOANS was here, and continues with the new value. The warnings appear without-w, and$VERBOSE = nilhides them. RuboCop’sLint/ConstantReassignmentcop reports the case statically; it is a newer cop that is pending, so it is not enabled by default. -
constantsreturns the constants of the module and, for a module that includes others or a class with a superclass, the inherited ones;constants(false)returns only the module’s own. -
const_getaccepts a symbol or a string and raisesNameError: wrong constant name lost_feefor a name that is not a valid constant name.const_defined?is the non-raising check. Both take aninheritargument:true(the default) follows the ancestors, and for a module also checksObject;falselooks only in the receiver. -
const_setcreates or replaces a constant dynamically, andremove_const(a private method, so called withsendhere) deletes one and returns its value. -
A failed lookup raises
NameErrorwithuninitialized constant Policy::LOST_FEE; the exception exposesnameandreceiver. -
const_source_location(Ruby 2.7) returns the file and line of the last assignment, so it points at line 6, not 5.
Constant definition rules
A constant cannot be assigned inside a method body. The parser rejects it:
# frozen_string_literal: true
source = <<~RUBY
def configure
DEFAULT_DAYS = 14
end
RUBY
begin
eval(source, binding, 'config.rb', 1) # rubocop:disable Security/Eval
rescue SyntaxError => e
puts e.message
end
# Constants are fine in a class body, and const_set works inside a method.
class Config
def self.setup = const_set(:DEFAULT_DAYS, 14)
end
p Config.setup
p Config::DEFAULT_DAYS
config.rb:2: syntax error found
1 | def configure
> 2 | DEFAULT_DAYS = 14
| ^~~~~~~~~~~~~~~~~ dynamic constant assignment
3 | end
14
14
Source: Modules and Classes: Constants
The SyntaxError explains dynamic constant assignment. Inside a method, use const_set (a method call, evaluated when
it runs) or restructure so the constant is defined once, in the class body. Defining a constant from a method is rarely
the right design; a memoized value in an instance variable of the class or module is usually what is meant. Constants
may be defined in class, module and Class.new { } bodies, at the top level, and through const_set.
Blocks are a source of confusion: a block keeps the lexical scope where it was written, so a constant assigned inside
Struct.new(...) do ... end or Desk.class_eval do ... end lands in the enclosing scope, usually Object. A string
passed to class_eval is compiled with the receiver as its scope, so a constant assigned in it lands on the receiver.
# frozen_string_literal: true
# A class whose constants are defined from outside.
class Desk
end
# A block keeps the lexical scope of where it was written (top level).
# rubocop:disable Lint/ConstantDefinitionInBlock
Desk.class_eval do
FROM_BLOCK = 1
end
# rubocop:enable Lint/ConstantDefinitionInBlock
# A string is compiled with the receiver as the lexical scope.
Desk.class_eval('FROM_STRING = 2', __FILE__, __LINE__)
p Object.const_defined?(:FROM_BLOCK)
p Desk.const_defined?(:FROM_BLOCK, false)
p Desk.constants
true
false
[:FROM_STRING]
Source: Module#class_eval
FROM_BLOCK ended up on Object, and FROM_STRING on Desk. RuboCop’s Lint/ConstantDefinitionInBlock is the cop for
this mistake; to define a constant on another class, use const_set on that class.
Source: Modules and Classes: Constants
Constants do not freeze values
A constant is a name that should not be rebound. It says nothing about the object it points to, which can still be mutated through any reference. The convention for collections is to freeze them, and to freeze what they contain.
# frozen_string_literal: true
# Lending states and rules; a constant name does not make its value immutable.
module Rules
STATES = %i[available lent overdue returned] # rubocop:disable Style/MutableConstant
FEES = { late: [1, 2], lost: [20] }.freeze
NAMES = %w[Dune Emma].map(&:freeze).freeze
CALENDAR = Ractor.make_shareable({ open: %w[mon tue], closed: %w[sun] })
end
Rules::STATES << :lost
p Rules::STATES
Rules::FEES[:late] << 3 # the hash is frozen, the array inside it is not
p Rules::FEES
begin
Rules::FEES[:extra] = []
rescue FrozenError => e
puts e.message
end
begin
Rules::NAMES << 'Ulysses'
rescue FrozenError => e
puts e.message
end
begin
Rules::CALENDAR[:open] << 'wed'
rescue FrozenError => e
puts e.message
end
p Rules::CALENDAR.frozen?, Rules::CALENDAR[:open].frozen?
p Ractor.shareable?(Rules::FEES), Ractor.shareable?(Rules::CALENDAR)
editable = Rules::CALENDAR.dup # shallow copy: unfrozen hash, same frozen arrays
editable[:holiday] = %w[dec25]
p editable.frozen?, editable[:open].frozen?
[:available, :lent, :overdue, :returned, :lost]
{late: [1, 2, 3], lost: [20]}
can't modify frozen Hash: {late: [1, 2, 3], lost: [20]}
can't modify frozen Array: ["Dune", "Emma"]
can't modify frozen Array: ["mon", "tue"]
true
true
false
true
false
true
Source: Ractor.make_shareable
-
STATESwas not frozen, so<<changed a "constant" shared by the whole program. TheStyle/MutableConstantcop reports this assignment. -
FEESis frozen, so adding a key raisesFrozenError(can't modify frozen Hash: {...}). Freezing is shallow: the array stored under:latewas not frozen and accepted<< 3. -
NAMESfreezes each string and the array, which makes the whole structure immutable. -
Ractor.make_shareablefreezes an object graph deeply (and returns it), which is the portable way to make a constant immutable all the way down.Ractor.shareable?tests whether a value qualifies. Only shareable constants can be read from a non-main Ractor; see Ractors. -
dupreturns an unfrozen shallow copy: the hash can change, and the arrays inside are the same frozen objects.
With # frozen_string_literal: true (the standard first line of a library file), string literals are frozen. Without it,
Ruby 3.4 and 4.0 treat them as chilled: mutating one works but can emit a deprecation warning under -W:deprecated.
See Literals and
Object Lifecycle, Freezing and Copying.
The shareable_constant_value magic comment
Ruby 3.0 added a magic comment that freezes constants for you. It applies to the constants assigned after it, in the current lexical scope, and has four modes:
| Mode | Effect |
|---|---|
|
no change; constants are assigned as written |
|
literals assigned to constants are deeply frozen; any other value must already be Ractor-shareable, or |
|
every assigned value is made shareable with |
|
every assigned value is deep-copied and the copy made shareable, leaving the original untouched |
The experimental_* modes are documented as experimental since 3.0; this page does not recommend them in production
code. The literal mode is the safe one.
# frozen_string_literal: true
# shareable_constant_value: literal
# Fees in cents; with the directive, literal values are deeply frozen.
module Fees
LATE = { per_day: [10, 25], cap: 500 }
CODES = %w[L M]
end
p Fees::LATE.frozen?, Fees::LATE[:per_day].frozen?
p Ractor.shareable?(Fees::CODES)
begin
Fees::CODES << 'X'
rescue FrozenError => e
puts e.message
end
# const_set is not affected by the directive.
Fees.const_set(:LIST, [])
p Fees::LIST.frozen?
true
true
true
can't modify frozen Array: ["L", "M"]
false
Both LATE and its inner array are frozen without any .freeze call, and CODES << 'X' raises FrozenError. The
directive does not touch const_set, so Fees::LIST stays unfrozen. A value that is not a literal is rejected:
# frozen_string_literal: true
# shareable_constant_value: literal
# A value built at load time is not a literal, so it must already be shareable.
reader = Struct.new(:name).new('Ana')
begin
CURRENT_READER = reader
rescue Ractor::IsolationError => e
puts "#{e.class}: #{e.message}"
end
Ractor::IsolationError: cannot assign unshareable object to CURRENT_READER
Source: Comments: Mode literal
Ractor::IsolationError says cannot assign unshareable object to CURRENT_READER. To fix it, build the object from a
literal, freeze it, or call Ractor.make_shareable(reader) before assigning. Because the directive is read per scope, a
file can switch modes by repeating the comment, and it covers only the module or class body where it appears.
Lookup: lexical scope, then ancestors
A reference to a bare constant NAME is resolved in this order:
-
The lexical scopes in
Module.nesting, from the innermost outward, each searched for its own constants only (not its ancestors). The top-levelObjectis not part of this step. -
The ancestors of the innermost lexical scope (
Module.nesting.first), in method-resolution order, including included modules and the superclass chain. For a class this reachesObject. -
If the innermost scope is a module (which has no
Objectamong its ancestors),Objectis searched too, so top-level constants stay visible. -
If nothing was found, Ruby calls
const_missing(:NAME)on the innermost scope. The default raisesNameError.
Module.nesting, innermost first?"} B -->|yes| Z["Use that constant"] B -->|no| C{"Defined in the ancestors of
Module.nesting.first?"} C -->|yes| Z C -->|no| D{"Innermost scope is a module?
Then check Object as well"} D -->|found| Z D -->|not found| E["Call const_missing with :NAME
on the innermost scope"] E -->|returns a value| Z E -->|default: raises| F["NameError: uninitialized constant"]
The point is that the text of the program decides which scopes count, not the class of the object running the method.
# frozen_string_literal: true
DAYS = 'top level'
# A kind of lendable item.
class Media
KIND = 'media'
DAYS = 'media'
end
# Lexical scope is searched before ancestors.
module Lending
KIND = 'lending'
p Module.nesting
# A book that can be lent.
class Book < Media
p Module.nesting
# Lending::KIND is found lexically, before Media::KIND.
def kind = KIND
# Not in Lending, so Media::DAYS is found through the ancestors.
def days = DAYS
# Nothing nearer, so Object (through the ancestors) provides it.
def comparable = Comparable
end
end
# The compact form: Lending is not in Module.nesting, so its constants are not visible.
# rubocop:disable Style/ClassAndModuleChildren
class Lending::Dvd < Media
p Module.nesting
# Media::KIND, not Lending::KIND.
def kind = KIND
end
# rubocop:enable Style/ClassAndModuleChildren
p Lending::Book.new.kind
p Lending::Book.new.days
p Lending::Book.new.comparable
p Lending::Dvd.new.kind
[Lending]
[Lending::Book, Lending]
[Lending::Dvd]
"lending"
"media"
Comparable
"media"
Source: Module.nesting
-
Module.nestingis[Lending]at the module level and[Lending::Book, Lending]insideBook. It lists the lexical scopes, innermost first. -
Book#kindfindsKINDinLending(step 1) before it ever reachesMedia::KIND(step 2, through the superclass). A nearer lexical definition beats an inherited one. -
Book#dayshas noDAYSinLending, so step 2 findsMedia::DAYSthrough the ancestors, not the top-levelDAYS. -
Book#comparablefindsComparablethroughObject, which is in `Book’s ancestors. -
With the compact
class Lending::Dvd < Media,Lendingis not inModule.nesting([Lending::Dvd]), soDvd#kindskipsLending::KINDand returns the inherited'media'. The nested form (module Lending; class Dvd) and the compact form are not interchangeable when constants are involved. RuboCop’sStyle/ClassAndModuleChildrenprefers the nested form.
A constant reached through a mixin is an ancestor constant, so it is not seen from every lexical position:
# frozen_string_literal: true
# Constants come from the lexical scope, then the ancestors, including mixins.
module Greeting
WORD = 'hello'
end
# A class that mixes the module in and defines one constant of its own.
class Desk
include Greeting
OWN = 'own'
def word = WORD
class << self
# Desk is in the lexical scope of the singleton class body.
def own_word = OWN
# Greeting is only an ancestor of Desk, and lexical lookup ignores ancestors.
def mixin_word = WORD
end
end
p Desk.new.word, Desk.own_word, Desk::WORD
begin
Desk.mixin_word
rescue NameError => e
puts e.message
end
p Desk.const_defined?(:WORD, false)
p Desk.constants.sort
"hello"
"own"
"hello"
uninitialized constant #<Class:Desk>::WORD
false
[:OWN, :WORD]
Source: Modules and Classes: Constants
Inside class << self, the lexical scope includes Desk (so OWN, defined directly in Desk, is found), but the
ancestors searched are those of the singleton class, which does not include Greeting. Desk::WORD and the instance
method word both work, since they search the ancestors of Desk. Desk.constants lists WORD because the inherited
ones count, while const_defined?(:WORD, false) is false. When a mixin’s constant must be visible from class methods,
qualify it (Greeting::WORD).
Qualified lookup and the top level
The :: operator looks a constant up in one specific module and its ancestors, skipping the lexical scopes. A leading
:: starts at Object.
# frozen_string_literal: true
DAYS = 'top level'
# A kind of lendable item.
class Media
KIND = 'media'
end
# Items that can be lent.
module Lending
# A book that can be lent.
class Book < Media
end
end
p Lending::Book::KIND # scoped lookup: Book, then its ancestors
p ::DAYS # an explicit top-level constant
p Lending.const_get(:DAYS)
begin
Lending.const_get(:DAYS, false) # inherit = false: only Lending itself
rescue NameError => e
puts e.message
end
begin
Lending::Book::String # scoped lookup does not fall back to Object
rescue NameError => e
puts e.message
end
p Lending::Book.const_get(:String) # const_get does
p Lending::Book.const_get('Media::KIND')
"media"
"top level"
"top level"
uninitialized constant Lending::DAYS
uninitialized constant Lending::Book::String
String
"media"
Source: Module#const_get
-
Lending::Book::KINDfindsMedia::KINDthrough `Book’s ancestors. -
::DAYSnames the top-level constant explicitly, which is useful when a nearerDAYSwould otherwise win. -
Lending.const_get(:DAYS)returns the top-level value, becauseconst_getwithinherittrue checksObjectfor a module. Withfalseit raisesuninitialized constant Lending::DAYS. -
A scoped reference such as
Lending::Book::Stringdoes not fall back toObject; it raisesNameError. Ruby 2.5 removed the old behaviour that returned top-level constants through a class path with a warning.Book.const_get(:String)still reaches it, becauseconst_getfollowsObjectby design. -
const_getaccepts a path as a string,'Media::KIND', and walks it one segment at a time.
Top-level constants are defined on Object. Every class and every module (through step 3) can therefore see them, and
a top-level class Policy is simply the constant Object::Policy. Keep application constants in a namespace so they do not
collide with a gem’s constants.
Hiding and retiring constants
private_constant makes a constant unreachable with the Module::NAME syntax while leaving it usable by name inside the
module. deprecate_constant keeps a constant working but makes references to it print a deprecation warning.
# frozen_string_literal: true
# Item kinds known to the catalogue.
module Kinds
BOOK = :book
LEGACY_MAGAZINE = :magazine
INTERNAL_CODES = %w[b m].freeze
private_constant :INTERNAL_CODES
deprecate_constant :LEGACY_MAGAZINE
# Inside the module the private constant is an ordinary name.
def self.codes = INTERNAL_CODES
end
p Kinds.codes
p Kinds::LEGACY_MAGAZINE
begin
Kinds::INTERNAL_CODES
rescue NameError => e
puts e.message
end
p Kinds.constants
p Kinds.const_defined?(:INTERNAL_CODES)
p Kinds.const_get(:INTERNAL_CODES) # const_get ignores private_constant
["b", "m"]
private_deprecated_constants.rb:17: warning: constant Kinds::LEGACY_MAGAZINE is deprecated
:magazine
private constant Kinds::INTERNAL_CODES referenced
[:BOOK, :LEGACY_MAGAZINE]
true
["b", "m"]
Source: Module#private_constant
The output is for ruby -W:deprecated private_deprecated_constants.rb; deprecation warnings are off in a plain run.
-
Inside the module,
INTERNAL_CODESis an ordinary name, soKinds.codesworks. -
Kinds::INTERNAL_CODESraisesNameError: private constant Kinds::INTERNAL_CODES referenced. -
constantsomits private constants, butconst_defined?still saystrue, andconst_getignores the privacy and returns the value. Private constants are an API signal, not a security mechanism. -
public_constantreversesprivate_constant. -
If the module defines
const_missing, a reference to one of its private constants callsconst_missinginstead of raising the privacy error:
# frozen_string_literal: true
# A module that hides one constant and also defines const_missing.
module Vault
SECRET = 'demo'
private_constant :SECRET
def self.const_missing(name)
"const_missing called for #{name}"
end
end
puts Vault::SECRET # the private constant reaches const_missing
const_missing called for SECRET
Source: Module#private_constant
That interaction can hide a mistake. If you define const_missing and also use private_constant, check that the hook
re-raises with super for names it does not recognise.
const_missing, const_added and autoload
When lookup fails, const_missing(name) is called on the innermost scope. Its result becomes the value of the reference.
Code loaders use it to load a file on demand and gems use it to generate constants.
# frozen_string_literal: true
# Shelf codes resolved on demand: Codes::FICTION gives 'F', Codes::HISTORY gives 'H'.
module Codes
# Called when a constant is not found anywhere in the lookup path.
def self.const_missing(name)
code = name.to_s[0]
return super if code.nil? || name.to_s.length < 4
const_set(name, code) # define it so const_missing runs only once
end
end
p Codes::FICTION
p Codes.constants
begin
Codes::X
rescue NameError => e
puts e.message
end
"F"
[:FICTION]
uninitialized constant Codes::X
Source: Module#const_missing
const_set inside the hook defines the constant so the hook runs only once per name. return super for names the hook
does not handle keeps the standard NameError. A const_missing that returns a value without defining the constant runs
again for every reference, and one that hides typos by returning a default makes failures harder to find; use it with
care. The standard, declarative alternative is Module#autoload (and, for whole applications, a code loader such as
Zeitwerk), covered in
Code Loading: require and autoload.
Module#const_added (Ruby 3.2) is the opposite hook: it runs each time a constant is defined, including constants that
name nested classes and modules.
# frozen_string_literal: true
# Module#const_added (3.2) runs every time a constant is defined.
class Registry
def self.const_added(name)
super
puts "defined #{self}::#{name}"
end
LIMIT = 3
# A nested class is a constant too.
class Entry; end
end
p Ruby::VERSION == RUBY_VERSION
defined Registry::LIMIT
defined Registry::Entry
true
Source: Module#const_added
Define const_added before the constants it should observe, and call super. The hook gives registries, DSLs and
documentation tools a supported alternative to polling constants.
Source: Module#const_missing
Naming anonymous classes
A class created with Class.new has no name until it is assigned to a constant. The first constant that receives it
names it, and later constants are aliases that leave the name alone.
# frozen_string_literal: true
# An anonymous class gets its name the first time it is assigned to a constant.
branch = Class.new
p branch.name
Annex = branch # rubocop:disable Naming/ConstantName
p branch.name, branch
Other = branch # rubocop:disable Naming/ConstantName
p Other.name # the first constant wins
temp = Module.new.set_temporary_name('fake<shelf>') # 3.3
p temp
Permanent = temp # rubocop:disable Naming/ConstantName
p temp.name
nil
"Annex"
Annex
"Annex"
fake<shelf>
"Permanent"
Source: Module#set_temporary_name
Module#set_temporary_name (Ruby 3.3) gives an anonymous class or module a name for debugging, and the name must not be
a valid constant path (fake<shelf> is fine, Fake is rejected). Assigning the module to a real constant later replaces
the temporary name with the permanent one.
Checking existence with defined?
defined?(expression) returns a short description of the expression or nil, without evaluating the expression. It is
a keyword, not a method, and it never raises NameError for the name it checks.
# frozen_string_literal: true
LIMIT = 3
title = 'Dune'
@reader = 'Ana'
checks = {
local: defined?(title),
ivar: defined?(@reader),
missing_ivar: defined?(@other),
constant: defined?(LIMIT),
method: defined?(puts),
call: defined?(title.upcase),
bad_call: defined?(title.nothing),
assign: defined?(_copy = title),
self: defined?(self),
literal: defined?(42),
nil: defined?(nil),
operator: defined?(1 + 1),
global: defined?($stdout),
undefined: defined?(nothing_here)
}
checks.each { |key, value| puts "#{key}: #{value.inspect}" }
def with_block = defined?(yield)
p with_block
p(with_block { nil })
# A base type and a subtype that asks whether super exists.
class Base
def greet = 'hi'
end
# The subtype.
class Child < Base
def greet = defined?(super)
def other = defined?(super)
end
p Child.new.greet
p Child.new.other
local: "local-variable"
ivar: "instance-variable"
missing_ivar: nil
constant: "constant"
method: "method"
call: "method"
bad_call: nil
assign: "assignment"
self: "self"
literal: "expression"
nil: "nil"
operator: "method"
global: "global-variable"
undefined: nil
nil
"yield"
"super"
nil
Source: Miscellaneous Syntax: defined?
| Expression | Result |
|---|---|
local variable |
|
|
|
|
|
|
|
constant |
|
method call |
|
|
|
assignment |
|
|
|
other literals |
|
|
|
Use it for optional integration (defined?(Rails)), to guard yield, and to check a variable without reading it.
Prefer respond_to? when a method on an object matters, since defined?(obj.meth) only reports whether the call could
be made, and prefer Module#const_defined? or const_get when the name arrives as a string.
Source: Miscellaneous Syntax: defined?
Variables and constants in the shelf gem
The shelf gem keeps its mutable state in one place and its fixed data in constants. Shelf.config is a
module-level instance variable created lazily with ||=, and Shelf::Loan::STATES is a frozen constant array.
# frozen_string_literal: true
require 'shelf'
p Shelf.instance_variables # no state until first use
p Shelf.config.loan_days
p Shelf.instance_variables # @config was created lazily by ||=
p Shelf.constants.sort
p Shelf::Loan::STATES, Shelf::Loan::STATES.frozen?
p Shelf::Loan.constants
p Shelf::Loan.name, Shelf::Loan.const_source_location(:STATES).last
Shelf.configure { |config| config.loan_days = 21 }
p Shelf.config.loan_days
p Shelf::Config.members
[]
14
[:@config]
[:Author, :Book, :Catalog, :Config, :Error, :Isbn, :Loan, :Refinements, :Store, :VERSION]
[:available, :lent, :overdue, :returned]
true
[:STATES]
"Shelf::Loan"
6
21
[:store, :loan_days]
Source: Module#const_source_location
-
Shelf.instance_variablesis empty until the first call toShelf.config, which creates@configand caches it. The module itself is the object holding the variable. -
Loan::STATESis frozen, listed byShelf::Loan.constants, andconst_source_location(:STATES)points at line 6 of the file that defined it. -
Shelf.configureyields the configuration object, so the same instance is updated and read back as21afterwards. -
The
@config ||= ...idiom is not thread-safe if two threads race to initialize it; initialize eagerly at load time, or guard with aMutexasCatalogdoes.
Naming conventions
| Name | Convention | Example and RuboCop cop |
|---|---|---|
Local variable, instance variable, method |
|
|
Class, module |
|
|
Constant |
|
|
Unused value |
a leading underscore, or |
|
Predicate method |
ends with |
|
Dangerous or in-place method |
ends with |
|
Global variable |
|
|
The ? and ! suffixes belong to method names, so a variable never ends with them. The Ruby style guide, linked from
the references below, is the community source for these rules, and
Idioms, Style and Clean Code collects the rest.
Coming from other languages
| Language | Familiar idea | In Ruby |
|---|---|---|
JavaScript |
|
|
Python |
|
blocks close over outer variables and assign to them with no keyword; a bare name inside |
Java, C# |
|
a class-level instance variable or a constant replaces a |
Go |
package-level |
module constants are public unless |
Rust |
immutable by default, shadowing with |
everything is mutable unless frozen; reassigning a local simply replaces the binding |
Where older code and books differ
Code and books written for Ruby 2.x or early 3.x show behaviour that has changed. The table lists the differences for the topics on this page.
| Older text or code | Current behaviour (Ruby 4.0) |
|---|---|
An error such as |
|
Reading an unset |
no warning since 3.0 |
An overtaken class variable only warns, and class variables are accepted at the top level |
|
|
|
|
the taint methods have had no effect since 2.7 and |
Setting |
deprecated since 2.7; pass the separator as an argument |
|
|
Block parameters named like an outer local warn about shadowing |
no warning since 2.6 |
|
|
Constants are frozen "by being constants" |
they are not; freeze them, or use |
A constant written inside |
it belongs to the enclosing scope; use |
|
it is an ActiveSupport method and not part of core Ruby; core Ruby has |
Memoized `@instance |
|
= new` singletons |
not thread-safe; see Singleton Classes and Class-Level Behavior for the |
Common mistakes
| Mistake | Fix |
|---|---|
|
write |
A variable assigned in an |
initialize it before the branch, or test it with |
Expecting a |
pass it as an argument, store it in a constant or instance variable, or use |
|
use a class-level |
Using |
use an object, a constant, |
Treating |
freeze it (deeply), or use |
Redefining a constant in a gem or in tests |
reopen the module and use |
|
read it inside the module, or expose a method |
Relying on a constant being visible inside |
qualify it with its module |
Defining constants inside a method or via |
use |
Metaprogramming with |
use |
See also Common Mistakes and Gotchas for the rest of the section.
Version notes
| Release | Change |
|---|---|
Ruby 2.5 |
|
Ruby 2.6 |
no more "shadowing outer local variable" warning for block parameters |
Ruby 2.7 |
|
Ruby 3.0 |
uninitialized instance variable reads do not warn; overtaken class variables and toplevel class-variable access raise |
Ruby 3.2 |
|
Ruby 3.3 |
|
Ruby 3.4 |
error messages use |
Ruby 4.0 |
|
Ruby 4.1 (preview) |
not released at the time of writing; this page makes no claims about it |
References
-
Ruby 4.0: Assignment (local variable scope, instance, class and global variables)
-
Ruby 4.0: Modules and Classes (nesting, scope and constants)
-
Ruby 4.0: Comments (the
shareable_constant_valuedirective) -
Ruby 4.0: Miscellaneous Syntax (
defined?,alias,BEGINandEND) -
Ruby 4.0 NEWS and the NEWS files for 3.0, 3.2, 3.3 and 3.4
-
The Ruby Style Guide and RuboCop Style cops (
Style/ClassVars,Style/GlobalVars,Style/MutableConstant) and Lint cops