Singleton Classes and Class-Level Behavior
|
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); features newer than Ruby 3.0 name the version that
introduced them. In Ruby a class is an object, so a "class method" is nothing special: it is a method that belongs to one
object (the class) rather than to every instance of it. The mechanism that makes this possible is the singleton class,
a hidden class that Ruby can attach to any single object to hold the methods and mixins that only that object has. This
page covers the mechanism and everything built on it: singleton methods on ordinary objects, singleton_class and
class << self, define_singleton_method, the three ways to write a class method and how class methods are inherited,
what changes inside class << self (constants, visibility, accessors), state kept at class level (class-level
instance variables versus @@ class variables), extend, private constructors with named factories, what dup,
clone, freeze and Marshal do with singleton state, and the Singleton design pattern, including why the hand-rolled
@instance ||= new idiom is not thread-safe and what to use instead.
It builds on Classes and Objects and
Modules and Mixins (include, prepend and extend), and on
Variables, Constants and Scope for the scope rules of
@, @@ and constants. The exact order in which Ruby searches the singleton class, prepended modules, the class, included
modules and the superclasses is the subject of Method Lookup and super;
Visibility and Encapsulation treats private and
protected in full; Dynamic Dispatch and Method Definition
and Hooks, eval and DSLs cover define_method, inherited,
extended and instance_eval in depth; Object Lifecycle, Freezing and Copying
covers dup, clone and freeze; Threads and the GVL and
Ractors explain the concurrency limits that matter for shared class-level
state; and SOLID and Design Patterns places the Singleton
pattern among the others. For comparison, see how Java,
Python (and its metaclasses),
Kotlin (object and companion objects),
C#, Rust
(associated functions) and Swift attach behavior to a
type rather than to an instance.
Every example was syntax-checked with ruby -wc, run with ruby -w on Ruby 4.0.7 and linted with RuboCop 1.92 using
the configuration of the running shelf gem (double-quoted strings, NewCops: enable, Style/Documentation off). The
output shown is the real output. Where an example deliberately breaks a cop to show a pitfall, or defines several classes
in one file for brevity, an inline rubocop:disable comment says so. The examples use the shelf gem (a small library
catalogue, see the shelf gem source) and are run with ruby -Ilib, so
require "shelf" finds it.
The idea in one page
Every Ruby object has a class, and a method call is looked up in that class and its ancestors. Sometimes one object needs behavior that no other instance of its class should have. Ruby does not copy the class for it; it lazily creates an anonymous class that is inserted in front of the object’s real class in the lookup path. That class is the singleton class (also called the eigenclass or, for classes, the metaclass; the official documentation says "singleton class").
| Concept | Meaning |
|---|---|
Singleton method |
A method defined on one object only: |
Singleton class |
The hidden class that stores an object’s singleton methods and the modules added with |
Class method |
A singleton method of a |
|
Includes a module into an object’s singleton class, so the module’s methods become singleton methods of that object (class methods when the object is a class). |
Class-level instance variable |
An |
Class variable |
A |
Singleton pattern |
A design that allows one instance of a class. Unrelated to singleton classes by mechanism, although both use the word. In Ruby it is implemented with |
The two meanings of "singleton" are worth keeping apart from the start. A singleton class is a language mechanism that every
object may have; the Singleton pattern is a design choice about how many instances a class may have. Class methods use the
first; the Singleton module and @instance ||= new implement the second, using class methods.
The lookup path for an object that has singleton behavior, and for a class (which is also an object that has a singleton class):
Singleton methods on one object
A method defined with the object as the receiver after def is a singleton method. The receiver can be a local
variable, a constant, self, or any expression in parentheses (def (expression).name). A literal such as def (1).twice
is a syntax error: "cannot define singleton method for literals".
# frozen_string_literal: true
require "shelf"
catalog = Shelf::Catalog.new
other = Shelf::Catalog.new
# A singleton method belongs to this one object only.
def catalog.size = count
catalog.add(Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz")))
puts catalog.size
p catalog.singleton_methods
p other.singleton_methods
p other.respond_to?(:size)
begin
other.size
rescue NoMethodError => e
puts e.message
end
1
[:size]
[]
false
undefined method 'size' for an instance of Shelf::Catalog
Source: Object#singleton_methods
singleton_methods lists the public and protected methods in the object’s singleton class and, by default, in the modules
the object was extended with (singleton_methods(false) restricts it to the singleton class itself). The second catalog
did not get the method: the receiver of def was catalog, not Shelf::Catalog. A def obj.name evaluates obj once,
at the moment of the definition; if the variable is later reassigned, the method stays with the original object.
Inside any method body, def self.name defines a singleton method on whatever self is at that moment, which is the
instance, not the class. That works, but it is almost always a mistake: each call creates a new singleton method (and a
singleton class) on that object.
Singleton methods are cheap to write and expensive to reason about. They are visible in methods, in singleton_methods
and in method(:name).owner, but not in the object’s class, so reading the class definition does not tell you what the
object can do. Prefer a module, a decorator or a subclass for behavior that more than one place needs; reserve per-object
methods for test doubles, stubs, DSL receivers and one-off configuration.
The singleton class
singleton_class returns the object’s singleton class, creating it if needed. class << obj ... end opens it for
definitions, and class << obj; self; end is the pre-singleton_class way of getting it. Every object has at most one,
and singleton_class returns the same object each time.
# frozen_string_literal: true
require "shelf"
catalog = Shelf::Catalog.new
sc = catalog.singleton_class
puts sc.inspect.sub(/0x\h+/, "0x...")
p sc.singleton_class?
p Shelf::Catalog.singleton_class?
p sc.attached_object.equal?(catalog)
p sc.superclass
p sc.instance_methods(false)
catalog.define_singleton_method(:titled) { |prefix = "#"| "#{prefix}#{count}" }
p catalog.titled
p catalog.titled("@")
p sc.instance_methods(false)
p catalog.singleton_method(:titled).arity
p catalog.method(:titled).owner == sc
begin
Shelf::Catalog.attached_object
rescue TypeError => e
puts e.message
end
#<Class:#<Shelf::Catalog:0x...>>
true
false
true
Shelf::Catalog
[]
"#0"
"@0"
[:titled]
-1
true
'Shelf::Catalog' is not a singleton class
Source: Object#singleton_class
Things to notice in the output:
-
Its
inspectis#<Class:#<Shelf::Catalog:0x...>>; for a class it is#<Class:Shelf::Catalog>. The address is scrubbed in this example because it changes on every run. -
singleton_class?(Module#singleton_class?) is true for a singleton class and false for a normal class. -
attached_object(Ruby 3.2, Class#attached_object) returns the one object the singleton class belongs to, and raisesTypeError("is not a singleton class") for any other class. -
The
superclassof an object’s singleton class is the object’s class, which is exactly the position in the lookup path that the diagram above shows. -
define_singleton_method(name, callable = nil, &block)is the metaprogramming form ofdef obj.name. It accepts a block, aProc, aMethodor anUnboundMethod, returns the name as a symbol, and creates the method with a block’sselfbound to the receiver and with the block’s closure intact.singleton_method(:name)fetches it as aMethodobject (raisingNameErrorif the object has no such singleton method). -
A singleton class cannot be instantiated ("can’t create instance of singleton class") or subclassed ("can’t make subclass of singleton class").
Which objects can have a singleton class
Every object that is a regular heap object can: strings, arrays, hashes, ordinary instances, classes and modules. A few cannot, because they have no identity of their own to attach a class to, or because they are shared:
# frozen_string_literal: true
[1, :sym, 1.5, nil, true, "literal", String.new("built"), [1]].each do |value|
label = value.inspect[0, 12].ljust(12)
shown = value.singleton_class.inspect.sub(/0x\h+/, "0x...")
puts "#{label} #{shown}"
rescue TypeError => e
puts "#{label} #{e.class}: #{e.message}"
end
def nil.blank? = true
p nil.blank?
p NilClass.instance_method(:blank?).owner
p 1.respond_to?(:blank?)
number = 42
begin
def number.double = self * 2
rescue TypeError => e
puts "#{e.class}: #{e.message}"
end
1 TypeError: can't define singleton
:sym TypeError: can't define singleton
1.5 TypeError: can't define singleton
nil NilClass
true TrueClass
"literal" TypeError: can't define singleton
"built" #<Class:#<String:0x...>>
[1] #<Class:#<Array:0x...>>
true
NilClass
false
TypeError: can't define singleton
| Object | Result of singleton_class |
|---|---|
|
|
|
Their own class, |
A string literal under |
|
Any other object |
A fresh singleton class, created on first request. |
Frozen objects can have a singleton class but cannot gain methods: defining a singleton method on a frozen object raises
FrozenError, as the section on copying and freezing below shows. Instances of Data (Shelf::Book, Shelf::Isbn) are
frozen from construction, so def book.shelf_mark fails with FrozenError; that is the reason the examples on this page
use Shelf::Catalog and Shelf::Loan as the receivers of singleton methods.
Class methods are singleton methods
A class is an instance of Class, so a class method is a singleton method of that object, stored in the class’s singleton
class. Ruby offers three spellings that produce the same method:
# frozen_string_literal: true
# Three spellings of the same thing: a method on the class object.
class Reader
def self.from_card(number) = new("card-#{number}")
def Reader.from_name(name) = new(name.downcase) # rubocop:disable Style/ClassMethods
class << self
def anonymous = new("guest")
private
def secret_name = "hidden"
end
attr_reader :name
def initialize(name)
@name = name
end
end
p Reader.from_card(7).name
p Reader.from_name("ADA").name
p Reader.anonymous.name
p Reader.singleton_methods.sort
p Reader.singleton_class.private_instance_methods(false)
p Reader.respond_to?(:secret_name)
p Reader.new("x").respond_to?(:from_card)
begin
Reader.secret_name
rescue NoMethodError => e
puts e.message
end
"card-7"
"ada"
"guest"
[:anonymous, :from_card, :from_name]
[:secret_name]
false
false
private method 'secret_name' called for class Reader
| Spelling | Use it when | Notes |
|---|---|---|
|
One or a few class methods; the default. |
|
|
Almost never. |
Repeats the class name, breaks on rename, and RuboCop’s |
|
Several class methods, private class methods, class-level |
Opens the singleton class; |
singleton_methods reports the three class methods (it sorted them for stable output); Reader.respond_to?(:secret_name)
is false because that method is private in the singleton class; instances do not respond to class methods because they
are in a different class. A private class method raises NoMethodError ("private method 'secret_name' called for class
Reader", with straight quotes since Ruby 3.4) when called with an explicit receiver, exactly like a private instance method.
How class methods are inherited
A subclass inherits its superclass’s class methods because the singleton class of a subclass is a subclass of the singleton
class of the superclass. The chain runs parallel to the normal class chain, and ends by joining it: the singleton class of
BasicObject has Class as its superclass.
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile
class Reader
def self.kind = "reader"
end
class Librarian < Reader
def self.kind = "#{super} with keys"
end
p Librarian.kind
p Librarian.singleton_class.superclass
p Librarian.singleton_class.superclass.equal?(Reader.singleton_class)
p Librarian.singleton_class.ancestors.first(4)
p Reader.singleton_class.superclass
p BasicObject.singleton_class.superclass
p Class.singleton_class.ancestors.first(3)
p Reader.singleton_class.singleton_class
p Reader.singleton_class.singleton_class.singleton_class
p Librarian.method(:kind).owner
p Librarian.method(:kind).super_method.owner
# rubocop:enable Style/OneClassPerFile
"reader with keys"
#<Class:Reader>
true
[#<Class:Librarian>, #<Class:Reader>, #<Class:Object>, #<Class:BasicObject>]
#<Class:Object>
Class
[#<Class:Class>, #<Class:Module>, #<Class:Object>]
#<Class:#<Class:Reader>>
#<Class:#<Class:#<Class:Reader>>>
#<Class:Librarian>
#<Class:Reader>
Source: Class#superclass
That chain is the reason three common facts hold:
-
Librarian.kindfindsReader.kindandsuperinside a class method calls the parent’s class method. -
Reader.newworks for every class:newis an instance method ofClass, andClassis where the class-method chain ends (Reader.method(:new).ownerisClass). -
Reader.name,Reader.instance_methods,Reader.include,Reader.attr_accessorand the rest of the methods ofModuleare reachable for the same reason;attr_accessorandprivateare not keywords, they are ordinary class methods called with an implicitself.
Singleton classes of singleton classes exist too (#<Class:#<Class:Reader>>), created lazily; the line printing them shows
that the pattern repeats without end. In practice you never need to go further than one level.
What changes inside the singleton class body
Inside class << self the current class (self and the default definition target) is the singleton class. That has
four consequences worth knowing.
# frozen_string_literal: true
class Branch
LIMIT = 3
class << self
attr_accessor :default_hours
def describe = "#{name}: limit #{LIMIT}, hours #{default_hours.inspect}"
# Defined in the singleton class, not in Branch.
DEFAULT = "main"
def default = DEFAULT
end
end
Branch.default_hours = "9-17"
puts Branch.describe
p Branch.default
p Branch.constants
p Branch.singleton_class.constants.sort
p Branch.const_defined?(:DEFAULT, false)
begin
Branch::DEFAULT
rescue NameError => e
puts e.message
end
puts Branch.singleton_class.const_get(:DEFAULT)
p Branch.new.respond_to?(:default_hours)
Branch: limit 3, hours "9-17"
"main"
[:LIMIT]
[:DEFAULT]
false
uninitialized constant Branch::DEFAULT
main
false
-
attr_accessorcreates class-level accessors (hereBranch.default_hours), backed by class-level instance variables, not instance accessors. -
A constant assigned inside
class << selfbelongs to the singleton class, not toBranch.Branch.constantsdoes not listDEFAULT,Branch::DEFAULTraisesNameError, and only the singleton class can resolve it. Constants that belong to the class go outside the block, whereLIMITlives;LIMITis still visible inside the block because constant lookup also follows the lexical scope outward. -
private,protectedandpublicinside the block apply to the following class-method definitions, giving private class methods (secret_namein the previous example). -
The block returns the value of its last expression, which is why
class << obj; self; endreturns the singleton class.
Visibility of class methods
A bare private changes the default visibility of the instance methods defined after it. It has no effect on def self.x,
because that method goes to the singleton class, whose default visibility is separate. This is a very common mistake,
and RuboCop’s Lint/IneffectiveAccessModifier exists for it. There are three correct ways to make a class method private:
# frozen_string_literal: true
class Vault
private
# "private" does not reach def self.x (deliberate offense).
def self.open_door = "still public" # rubocop:disable Lint/IneffectiveAccessModifier
def hidden_instance = "really private"
class << self
private
def real_secret = "private on the singleton class"
end
def self.helper = real_secret
private_class_method :helper
end
p Vault.open_door
p Vault.respond_to?(:real_secret)
p Vault.respond_to?(:helper)
p Vault.private_methods(false).sort
p Vault.new.respond_to?(:hidden_instance)
p Vault.send(:helper)
"still public"
false
false
[:helper, :inherited, :initialize, :real_secret]
false
"private on the singleton class"
Source: Module#private_class_method
-
privateinsideclass << self, as withreal_secret. -
private_class_method :nameafter the definition, as withhelper. Since the definition expression returns the method name,private_class_method def self.helper ... endworks too.public_class_methodis its inverse. -
The wrong way is
private def self.name, which fails withNameError("undefined method 'name' for class 'Vault'") becauseprivatelooks the name up among instance methods.
Vault.send(:helper) still calls it: visibility in Ruby is advisory, see
Visibility and Encapsulation.
State kept at class level
Two kinds of variable can hold data on a class, and they behave very differently.
Class-level instance variable (@name) |
Class variable (@@name) |
|
|---|---|---|
Where it lives |
On the class object, like any instance variable of any object. |
In the class where it is first assigned; shared by that class and every subclass. |
Inheritance |
Not inherited: each subclass starts with none ( |
Shared: a subclass reads and writes the same variable. |
Access |
|
|
Reflection |
|
|
Style guides |
Preferred. |
Avoided; RuboCop’s |
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Counter
@instances = 0 # class-level instance variable: belongs to Counter itself
@@created = 0 # class variable: shared with every subclass
class << self
attr_reader :instances
def track
@instances = (@instances || 0) + 1
@@created += 1
end
def created = @@created
end
def initialize
self.class.track
end
end
class SpecialCounter < Counter
end
Counter.new
SpecialCounter.new
SpecialCounter.new
p Counter.instances
p SpecialCounter.instances
p Counter.created
p SpecialCounter.created
p Counter.instance_variables
p SpecialCounter.instance_variables
p Counter.class_variables
p SpecialCounter.class_variables
p Counter.new.instance_variables
# rubocop:enable Style/OneClassPerFile, Style/ClassVars
1
2
3
3
[:@instances]
[:@instances]
[:@@created]
[:@@created]
[]
Source: Assignment: Class Variables
Counter.instances and SpecialCounter.instances differ because @instances is a separate variable on each class
object (and the reader attr_reader :instances sits in the singleton class of Counter, which SpecialCounter inherits).
@@created is one variable seen by both, and instances of Counter themselves have no instance variables from either
kind (Counter.new.instance_variables is empty). Note that @@created is read and written inside class << self: class
variable lookup uses the lexically enclosing class and skips singleton classes, so those methods still reach the class’s
variable.
Why class variables are a trap
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Parent
@@shared = "parent"
@value = "parent"
class << self
attr_accessor :value
def shared = @@shared
end
end
class Child < Parent
@@shared = "child" # reassigns the variable Parent sees
end
# rubocop:enable Style/OneClassPerFile, Style/ClassVars
p Parent.shared
p Child.value
Child.value = "set on child"
p Parent.value
p Child.value
begin
Object.class_variable_get(:@@missing)
rescue NameError => e
puts e.message
end
"child"
nil
"parent"
"set on child"
uninitialized class variable @@missing in Object
Source: Module#class_variable_get
-
Silent sharing.
Childassigned@@shared, andParent.sharednow returns the child’s value: there is one variable. Any subclass, including one in a plugin, can change what the parent and every sibling see. The class-level instance variable@valuehas no such leak:Child.value = ...leavesParent.valuealone. -
Ruby 3.0 errors. Reading or writing a class variable at the top level (outside any class) raises
RuntimeError("class variable access from toplevel"). Defining the same class variable in a subclass first and in the parent later raisesRuntimeErroron access, because the subclass’s variable has been "overtaken":
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile, Style/ClassVars
class Parent
LEVEL = 0
end
class Child < Parent
@@level = "child"
end
class Parent
@@level = "parent" # defined later in the superclass: the subclass is "overtaken"
end
begin
Child.class_variable_get(:@@level)
rescue RuntimeError => e
puts "#{e.class}: #{e.message}"
end
# rubocop:enable Style/OneClassPerFile, Style/ClassVars
RuntimeError: class variable @@level of Child is overtaken by Parent
Source: Ruby 3.0.0 NEWS
-
Thread safety.
@@count += 1is a read followed by a write. Concurrent threads can lose updates, and so can any class-level state; guard shared mutable class-level data with aThread::Mutex(see the lazy singleton below) or avoid it. -
Ractors. A non-main Ractor can read a class-level instance variable only if its value is shareable ("can not get unshareable values from instance variables of classes/modules from non-main Ractors"), cannot assign one ("can not set instance variables of classes/modules by non-main Ractors"), and cannot touch class variables at all ("can not access class variables from non-main Ractors"); each is a
Ractor::IsolationError. See Ractors.
The usual guidance is therefore: use class-level instance variables (with a class << self reader) for state that belongs to
one class, use a constant for state that does not change, and treat @@ as legacy.
Giving each subclass its own state
Because class-level instance variables are not inherited, a base class that wants every subclass to start with its own
collection initializes it in the inherited hook, which Ruby calls on the superclass with each new subclass.
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile
class Registry
@entries = []
class << self
attr_reader :entries
def inherited(subclass)
super
subclass.instance_variable_set(:@entries, [])
end
def register(name) = entries << name
end
end
class Books < Registry; end
class Authors < Registry; end
Registry.register(:base)
Books.register(:isbn)
Authors.register(:name)
p Registry.entries
p Books.entries
p Authors.entries
p Registry.subclasses.sort_by(&:name)
# rubocop:enable Style/OneClassPerFile
[:base]
[:isbn]
[:name]
[Authors, Books]
Source: Class#inherited
inherited must call super so that hooks higher in the chain still run, and runs before the subclass body is evaluated,
so Books already has its own @entries inside its class body. Class#subclasses (Ruby 3.1) lists the direct subclasses
(Registry.subclasses); it does not include the class itself or indirect descendants, and the order is not specified, which
is why the example sorts it. Lazy initialization in the reader (@entries ||= []) is an alternative to the hook when the
subclass never needs the value before first use.
Extending objects and classes with modules
extend(*modules) is singleton_class.include(*modules) plus the extended hook. The module’s instance methods become
singleton methods of the receiver: on an ordinary object they are per-object behavior, and on a class they are class
methods. When a second module is extended later, it is searched before the first.
# frozen_string_literal: true
require "shelf"
# rubocop:disable Style/OneClassPerFile
module Greeting
def greet = "Welcome to #{self.class}"
end
module Finders
def find_by_title(catalog, title) = catalog.find { |book| book.title == title }
end
class Archive < Shelf::Catalog
extend Finders # class methods: Finders goes into Archive's singleton class
end
# rubocop:enable Style/OneClassPerFile
archive = Archive.new
archive.extend(Greeting) # instance-level: Greeting goes into this one object's singleton class
puts archive.greet
p archive.singleton_class.include?(Greeting)
p(archive.singleton_class.ancestors.first(3).map { |mod| mod.inspect.sub(/0x\h+/, "0x...") })
p Archive.new.respond_to?(:greet)
p archive.is_a?(Greeting)
book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
archive.add(book)
p Archive.find_by_title(archive, "Ruby") == book
p Archive.singleton_class.include?(Finders)
p Archive.include?(Finders)
p Archive.singleton_class.ancestors.first(4)
# extend is the same as including into the singleton class
class << archive
include Finders
end
p archive.find_by_title(archive, "Ruby").title
Welcome to Archive
true
["#<Class:#<Archive:0x...>>", "Greeting", "Archive"]
false
true
true
true
false
[#<Class:Archive>, Finders, #<Class:Shelf::Catalog>, #<Class:Object>]
"Ruby"
Source: Object#extend
-
archive.singleton_class.ancestorslists the singleton class, thenGreeting, then the class, which is whereextendplaces a module.archive.is_a?(Greeting)is true afterextendbecauseis_a?consults the singleton class too; otherArchiveinstances are unaffected. -
extendon a class (extend FindersinArchive) is the idiom for class methods that live in a reusable module, such as thedef_delegatorofForwardable, which is made available withextend Forwardable. Theself.included(base)plusClassMethodsidiom built on top of it is covered in Modules and Mixins, together with theextendedandprependedhooks. -
Archive.include?(Finders)is false:extendandincludetarget different classes, which is why an extended module’s methods are not instance methods. -
Module#extend_objectis the hook that does the work and can be overridden (the Singleton module undefines it, so extending an object withSingletonfails).
module_function and extend self create class-level (module-level) copies of a module’s methods and are the usual way
to write a module of utility functions; see Modules and Mixins.
Defining singleton methods programmatically
define_singleton_method is the right tool when the name or the body is computed. instance_eval and class_eval are
frequently confused with each other because both take a block and both evaluate def, yet they define in different places:
# frozen_string_literal: true
require "shelf"
klass = Class.new
klass.instance_eval do
# a singleton method of klass
def made_by_instance_eval = :class_method
end
klass.class_eval do
# an instance method of klass
def made_by_class_eval = :instance_method
end
p klass.singleton_methods
p klass.instance_methods(false)
p klass.made_by_instance_eval
p klass.new.made_by_class_eval
obj = Object.new
obj.instance_eval { def only_here = :singleton }
p obj.singleton_methods
p obj.singleton_class.instance_methods(false)
p Object.new.respond_to?(:only_here)
# class << self opens the singleton class; self there is that class
inside = Object.new
captured = class << inside; self; end
p captured == inside.singleton_class
[:made_by_instance_eval]
[:made_by_class_eval]
:class_method
:instance_method
[:only_here]
[:only_here]
false
true
Source: BasicObject#instance_eval
-
instance_evalchangesselfto the receiver and the default definition target to the receiver’s singleton class. On a class that defines a class method; on an ordinary object it defines a singleton method. -
class_eval(Module#class_eval, docs) changesselfto the module and the default definition target to the module itself, sodefdefines instance methods. -
class << obj; self; endreturns the singleton class, and the singleton class’s owninstance_methods(false)are the methods you see insingleton_methods.
The singleton_method_added hook (a private method of BasicObject, called on the object whenever a singleton method is
added) and its relatives method_added, const_added and singleton_method_removed are the introspection side of the same
mechanism; define_singleton_method triggers it just as def does.
# frozen_string_literal: true
require "shelf"
# Generated class-level factories, one per loan state, plus a hook
# that records every singleton method defined after it.
class LoanFactory
class << self
attr_reader :added
def singleton_method_added(name)
super
(@added ||= []) << name
end
end
Shelf::Loan::STATES.each do |state|
define_singleton_method(:"#{state}_for") do |book|
Shelf::Loan.new(book, state:)
end
end
def self.manual = :manual
end
book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
puts LoanFactory.lent_for(book).describe
puts LoanFactory.returned_for(book).describe
p LoanFactory.singleton_methods(false).sort
p LoanFactory.added
Ruby is lent
Ruby came back
[:added, :available_for, :lent_for, :manual, :overdue_for, :returned_for, :singleton_method_added]
[:singleton_method_added, :available_for, :lent_for, :overdue_for, :returned_for, :manual]
The hook is defined first because it only sees methods added after its own definition, and it also reports itself and the
attr_reader. Production code rarely needs it; it is how libraries notice class-level definitions made by their users
(see Hooks, eval and DSLs).
Factories and a private constructor
Class methods are the place for alternative constructors. Ruby’s new is itself a class method (an instance method of Class)
that allocates an object and calls initialize. A class that wants every instance created through a named factory makes new
private in its singleton class and exposes factory methods, which can call new with an implicit receiver:
# frozen_string_literal: true
require "shelf"
# A class whose only construction path is a named factory.
class Receipt
attr_reader :loan, :due_in
class << self
def for(loan, days: Shelf.config.loan_days) = new(loan, days)
def count = @count ||= 0
def record = @count = count + 1
private :new
end
def initialize(loan, due_in)
@loan = loan
@due_in = due_in
self.class.record
end
end
book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
receipt = Receipt.for(Shelf::Loan.new(book, state: :lent))
p receipt.due_in
p Receipt.count
begin
Receipt.new(nil, 1)
rescue NoMethodError => e
puts e.message
end
p Receipt.allocate.due_in
p Receipt.singleton_class.private_instance_methods(false)
14
1
private method 'new' called for class Receipt
nil
[:new]
Source: Module#private
The pattern is the same one the Singleton pattern uses below. Three points:
-
A named factory (
Receipt.for(loan)) states the intent, can supply defaults from configuration, and can return a cached or a differently-typed object;Date.todayandTime.noware standard-library examples. -
private :newinsideclass << selfhides the constructor for outsiders, but not fromallocate:Receipt.allocatereturns an uninitialized instance (all instance variables arenil, asdue_inshows) andsend(:new)works. Privacy ofnewprevents accidents, not determined callers.Singletonalso makesallocateprivate. -
Keep
initializefree of I/O and heavy work. Once a factory exists, it is the natural place to open the file or read the configuration, and to pass the result tonew.
The same section of code shows that per-class bookkeeping (Receipt.count) is an ordinary class-level instance variable
memoized with ||= on first use.
Copying, freezing and Marshal
Singleton state interacts with the copying methods in a way that surprises people. dup makes a copy without singleton
methods or extended modules; clone copies them and the frozen state, unless freeze: false/freeze: true overrides it.
A frozen object cannot get new singleton methods. Marshal.dump refuses any object that has singleton methods.
# frozen_string_literal: true
require "shelf"
book = Shelf::Book.new(Shelf::Isbn.new("978-0-306-40615-7"), "Ruby", Shelf::Author.new("Matz"))
loan = Shelf::Loan.new(book)
loan.define_singleton_method(:label) { "main branch" }
p loan.dup.respond_to?(:label)
p loan.clone.respond_to?(:label)
loan.freeze
p loan.clone.frozen?
p loan.clone(freeze: false).frozen?
p loan.dup.frozen?
begin
loan.define_singleton_method(:other) { 1 }
rescue FrozenError => e
puts e.message.partition(": ").first
end
begin
Marshal.dump(loan.clone(freeze: false))
rescue TypeError => e
puts "#{e.class}: #{e.message}"
end
plain = Shelf::Loan.new(book)
p Marshal.load(Marshal.dump(plain)).state
false
true
true
false
false
can't modify frozen Shelf::Loan
TypeError: singleton can't be dumped
:available
Source: Kernel#clone
-
loan.duplostlabel;loan.clonekept it. Useclonewhen the singleton behavior is part of the object’s identity (test doubles, extended objects) anddupwhen you want a plain copy of the data. -
After
freeze,cloneis frozen andclone(freeze: false)is not;dupis never frozen. -
FrozenErroris raised when defining a singleton method on a frozen object, as it is for any other mutation. -
Marshal.dumpraisesTypeError("singleton can’t be dumped") for an object that has singleton methods. A plain object round-trips normally. The Singleton module, below, adds a_dump/_loadpair that makesMarshalreturn the one instance instead.
The Singleton pattern
The Singleton pattern guarantees a single instance of a class and a global way to reach it. It is the most overused of the classic patterns: a global with a constructor. It is legitimate for genuinely process-wide resources (a configuration registry, a connection to one device), and a poor default for anything that code or tests want to replace. Ruby offers several ways to get one-instance behavior. First, the version found in many tutorials and books:
The hand-rolled version and its race
# frozen_string_literal: true
class Registry
def self.instance
@instance ||= new
end
private_class_method :new
end
Source: Class#new
This reads well and is wrong under threads. @instance ||= new expands to a test of @instance for nil or false, followed by an assignment of the result of new,
and is a separate read and write, not one atomic step. The GVL does not make it atomic: it can switch threads between the
read and the write, and the window is exactly the time new takes to run. If constructing the object takes time (opening a
file, connecting, sleeping) several threads can pass the check before the first assignment, and each creates its own
instance. The race is invisible in tests with one thread and appears under load:
# frozen_string_literal: true
# The classic hand-rolled singleton, with a slow constructor
# (opening a file, a connection...) that makes the race visible.
class NaiveRegistry
@instance = nil
class << self
# "||=" is a read followed by a write: two steps, not one
def instance = @instance ||= new
private :new
end
def initialize
sleep 0.05
end
end
instances = Array.new(4) { Thread.new { NaiveRegistry.instance } }.map(&:value)
p instances.map(&:object_id).uniq.size
p instances.uniq.size
4
4
Source: Thread::Mutex
All four threads got different objects, so "singleton" is not guaranteed. The construction time was made long with
sleep to make the failure reproducible; with a fast constructor the same code fails rarely, which is worse. The same
||= caveat applies to any lazily created class-level state, and to the memoized class << self singleton helpers found in
many codebases.
Fix 1 and 2: create it eagerly, or lazily under a lock
The simplest correct version has no lazy step (EagerRegistry below). A constant is assigned while the file is loaded, and
require holds a lock on the file, so the assignment happens exactly once. When the instance is expensive and may never be
needed, take a Thread::Mutex around the check and the assignment (LazyRegistry), so only one thread runs the ||=.
# frozen_string_literal: true
# rubocop:disable Style/OneClassPerFile
# 1. Eager: the constant is created while the file loads, which is
# synchronised by the require lock.
class EagerRegistry
INSTANCE = new
def self.instance = INSTANCE
private_class_method :new
end
# 2. Lazy with a lock: check, then create, inside one critical section.
class LazyRegistry
@lock = Thread::Mutex.new
@instance = nil
class << self
def instance
@lock.synchronize { @instance ||= new }
end
private :new
end
def initialize
sleep 0.05
end
end
# rubocop:enable Style/OneClassPerFile
eager = Array.new(4) { Thread.new { EagerRegistry.instance } }.map(&:value)
lazy = Array.new(4) { Thread.new { LazyRegistry.instance } }.map(&:value)
p eager.uniq.size
p lazy.uniq.size
1
1
Source: Thread::Mutex#synchronize
Both variants return one object to all threads. Initialize the mutex in the class body, not lazily, otherwise the lock
itself is racy. Taking the lock on every call is cheap in MRI; if it ever matters, check @instance first outside the
lock and again inside (double-checked locking), which is what the standard library does.
Fix 3: the Singleton module
The singleton library (a default gem, version 0.3.0 in Ruby 4.0.7) packages the pattern. include Singleton makes new and
allocate private, adds a thread-safe instance class method, blocks dup and clone, gives every subclass its own
instance, and defines _dump/_load so that Marshal round-trips to the same instance. It implements the double-checked
lock described above with a mutex per class.
# frozen_string_literal: true
require "singleton"
class Settings
include Singleton
attr_accessor :loan_days
def initialize
@loan_days = 14
sleep 0.05
end
end
instances = Array.new(4) { Thread.new { Settings.instance } }.map(&:value)
p instances.map(&:object_id).uniq.size
p Settings.instance.equal?(Settings.instance)
begin
Settings.new
rescue NoMethodError => e
puts e.message
end
begin
Settings.instance.dup
rescue TypeError => e
puts "#{e.class}: #{e.message}"
end
begin
Settings.instance.clone
rescue TypeError => e
puts "#{e.class}: #{e.message}"
end
p Settings.include?(Singleton)
p Settings.singleton_class.private_method_defined?(:new)
p Settings.singleton_class.public_method_defined?(:instance)
p Marshal.load(Marshal.dump(Settings.instance)).equal?(Settings.instance)
1
true
private method 'new' called for class Settings
TypeError: can't dup instance of singleton Settings
TypeError: can't clone instance of singleton Settings
true
true
true
true
Source: Singleton
-
The four threads share one object although the constructor sleeps.
-
Settings.newfails withNoMethodError("private method 'new' called for class Settings") andSettings.allocatefails the same way;dupandcloneraiseTypeErrornaming the class. -
Marshal.load(Marshal.dump(instance))is the same object, because_loadcallsinstance. Override_dumpand_loadto keep state across serialization. -
Including
Singletonin a module raisesTypeError; extending an object with it is disabled. It targets classes only.
A subclass of a Singleton class is a separate singleton with its own instance and is initialized by the subclass’s
initialize:
# frozen_string_literal: true
require "singleton"
require "shelf"
# rubocop:disable Style/OneClassPerFile
class Settings
include Singleton
attr_accessor :loan_days
def initialize = @loan_days = 14
end
class StrictSettings < Settings
def initialize
super
@loan_days = 7
end
end
# Inject the collaborator instead of reaching for the global.
class Lender
def initialize(settings: Settings.instance) = @settings = settings
def days = @settings.loan_days
end
# rubocop:enable Style/OneClassPerFile
p Settings.instance.loan_days
p StrictSettings.instance.loan_days
p StrictSettings.instance.equal?(Settings.instance)
begin
Settings.allocate
rescue NoMethodError => e
puts e.message
end
p Lender.new.days
p Lender.new(settings: Shelf::Config.new(nil, 3)).days
14
7
false
private method 'allocate' called for class Settings
14
3
Source: Singleton
StrictSettings.instance is not Settings.instance. Note the Lender class: it takes its collaborator as a keyword
argument whose default is the global. Callers and tests can pass a different object (here a Shelf::Config with three loan
days) without touching the global, which is the single most useful habit when a Singleton exists. A singleton that
tests must reset (by reaching into its instance variables or reloading the file) is a signal that the code under test should
receive the object rather than look it up. The library also contains an undocumented Ractor-local variant; Ractors need
their own treatment (see Ractors).
Fix 4: module-level state without an instance
Often what the Singleton pattern is used for needs no instance at all. A module with its own state and module methods is a
one-of-a-kind object that Ruby gives you for free, and Shelf.config/Shelf.configure in the running example are exactly
that: a module-level instance variable behind a reader, with configure yielding it.
# frozen_string_literal: true
require "shelf"
# Module-level state, as Shelf.config does: no instance, no new.
module Clock
@offset = 0
class << self
attr_accessor :offset
def now = Time.at(1_800_000_000 + offset).utc
end
end
p Clock.now
Clock.offset = 3600
p Clock.now
p Clock.singleton_methods.sort
p Shelf.config.loan_days
p Shelf.configure { |config| config.loan_days = 21 }.loan_days
2027-01-15 08:00:00 UTC
2027-01-15 09:00:00 UTC
[:now, :offset, :offset=]
14
21
Source: Modules and Classes: Modules
Clock has no instance, no new and no instance; Clock.now and Clock.offset= are singleton methods of a module (which
is an object with a singleton class like any other). Shelf.configure returns the configuration, so the last line shows 21.
The lazy @config ||= ... inside Shelf.config is the same racy idiom as above and is safe in practice only when the
configuration is read once during start-up before threads exist; for state that is first touched concurrently, initialize it
in the module body, as Clock does.
| Approach | Choose it when | Trade-off |
|---|---|---|
Eager constant ( |
The object is cheap and always needed. |
Built at load time; failure at require time. |
Lazy with |
Creation is expensive or optional. |
A lock on every call (small). |
|
You want the guarantees without writing them. |
Global state; tests need injection; hides the lifecycle. |
Module with state ( |
No object semantics are needed (configuration, registry, clock). |
Cannot be subclassed or instantiated; still global. |
Pass the object in (dependency injection) |
Almost always, for anything tests or callers may replace. |
One more parameter to thread through. |
Class-level behavior in other languages
| Ruby | Closest equivalent elsewhere | Difference |
|---|---|---|
|
Java and C# |
A Ruby class method is a real method on a real object: inheritable, overridable with |
|
Python metaclass or |
The singleton class is an ordinary class that can include modules and take visibility keywords. |
Class-level instance variable |
Java |
Not inherited and not shared with subclasses; Ruby has no static fields. |
|
Java |
Shares a variable down the hierarchy, with the trap that any subclass can rebind it. |
|
Kotlin companion object implementing an interface; Python mixin on the metaclass |
Adds methods to one object, not just classes. |
|
Kotlin |
Not a language feature: a library module on top of private |
For the Kotlin object declaration, the C# static members and the Python metaclasses, see the pages linked in the
introduction; the Ruby model is closer to Python’s (everything is an object, including the class) than to Java’s, where
statics belong to no object.
Pitfalls
-
privatebeforedef self.xdoes nothing. Useprivate_class_methodor put the method inclass << self(Lint/IneffectiveAccessModifier). -
Constants in
class << selfare not the class’s constants. Define them outside the block. -
@@varis shared down the hierarchy. Use a class-level instance variable plus a reader, initialized ininheritedwhen each subclass needs its own. -
@instance ||= newis not thread-safe. Use an eager constant, a mutex, theSingletonmodule, or injection. -
def self.xinside an instance method defines a singleton method on that one instance each time it runs. -
dupdrops singleton methods andclonekeeps them;Marshal.dumprefuses objects that have them. -
Singleton methods on frozen objects (including every
Datainstance) raiseFrozenError; Integers, Symbols, Floats and frozen string literals cannot have singleton classes at all. -
private :newis not a lock.allocateandsend(:new)bypass it. -
Ractors. Class-level mutable state is the main thing that stops code being Ractor-safe.
RuboCop cops for this page
| Cop | What it checks |
|---|---|
|
Flags every |
|
|
|
|
|
Disabled by default. Enforces |
|
An access modifier that affects nothing, such as |
|
Group ( |
|
Hand-written readers and writers that should be |
|
|
|
Several top-level classes in one file (a new cop under |
Where older material is outdated
Books and blog posts that predate the current Ruby often show the Singleton pattern as @instance ||= new plus
private_class_method :new, and class-level data as @@var, without noting that the first is not thread-safe and the
second is shared with every subclass. The Singleton module, an eager constant, a mutex-guarded accessor or an injected
collaborator are better today. The terminology also moved: the official documentation says "singleton class" (the
informal "eigenclass" and "metaclass" still appear in older text), and singleton_class, Class#subclasses (3.1) and
Class#attached_object (3.2) replace the older class << obj; self; end idiom and manual tracking of subclasses.
Version notes
| Version | Change relevant to this page |
|---|---|
3.0 |
Reading or writing a class variable at the top level raises |
3.1 |
|
3.2 |
|
3.3 |
|
3.4 |
Error messages quote method names with apostrophes instead of a backtick and apostrophe. |
4.0 |
No change to the singleton-class model. The |
Summary
-
A singleton class belongs to one object, sits in front of its class in the lookup path, and holds
def obj.x,define_singleton_methodand extended modules. -
Class methods are singleton methods of the class object; they are inherited because singleton classes of subclasses inherit from singleton classes of superclasses.
-
def self.x,class << selfandextendare three ways to the same place;privateneedsprivate_class_methodorclass << self. -
Use class-level instance variables, not
@@, and initialize per-subclass state ininherited. -
The Singleton pattern is a design choice, separate from singleton classes. Make it thread-safe with an eager constant, a mutex or
include Singleton, and prefer injecting the object.
References
-
Ruby 4.0 documentation: Modules and Classes (the "Singleton Classes" section) and Assignment (class variables)
-
Object (
singleton_class,define_singleton_method,singleton_methods,singleton_method,extend,dup), Kernel (clone) and BasicObject (instance_eval,singleton_method_added) -
Module (
private_class_method,class_variable_get,extended,class_eval,singleton_class?) and Class (inherited,subclasses,attached_object,allocate) -
Ruby 3.0.0 NEWS, 3.1.0, 3.2.0 and 4.0.0
-
The Ruby Style Guide and RuboCop Style cops (
Style/ClassVars,Style/ClassMethods,Style/ClassMethodsDefinitions) and Lint cops (Lint/IneffectiveAccessModifier)