Hubbry Logo
Immutable objectImmutable objectMain
Open search
Immutable object
Community hub
Immutable object
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Immutable object
Immutable object
from Wikipedia

In object-oriented (OO) and functional programming, an immutable object (unchangeable[1] object) is an object whose state cannot be modified after it is created.[2] This is in contrast to a mutable object (changeable object), which can be modified after it is created.[3] In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.

Strings and other concrete objects are typically expressed as immutable objects to improve readability and runtime efficiency in object-oriented programming. Immutable objects are also useful because they are inherently thread-safe.[2] Other benefits are that they are simpler to understand and reason about and offer higher security than mutable objects.[2]

Concepts

[edit]

Immutable variables

[edit]

In imperative programming, values held in program variables whose content never changes are known as constants to differentiate them from variables that could be altered during execution. Examples include conversion factors from meters to feet, or the value of pi to several decimal places.

Read-only fields may be calculated when the program runs (unlike constants, which are known beforehand), but never change after they are initialized.

Weak vs strong immutability

[edit]

Sometimes, one talks of certain fields of an object being immutable. This means that there is no way to change those parts of the object state, even though other parts of the object may be changeable (weakly immutable). If all fields are immutable, then the object is immutable. If the whole object cannot be extended by another class, the object is called strongly immutable.[4] This might, for example, help to explicitly enforce certain invariants about certain data in the object staying the same through the lifetime of the object. In some languages, this is done with a keyword (e.g. const in C++, final in Java) that designates the field as immutable. Some languages reverse it: in OCaml, fields of an object or record are by default immutable, and must be explicitly marked with mutable to be so.

References to objects

[edit]

In most object-oriented languages, objects can be referred to using references. Some examples of such languages are Java, C++, C#, VB.NET, and many scripting languages, such as Perl, Python, and Ruby. In this case, it matters whether the state of an object can vary when objects are shared via references.

Referencing vs copying objects

[edit]

If an object is known to be immutable, it is preferred to create a reference of it instead of copying the entire object. This is done to conserve memory by preventing data duplication and avoid calls to constructors and destructors; it also results in a potential boost in execution speed.

The reference copying technique is much more difficult to use for mutable objects, because if any user of a mutable object reference changes it, all other users of that reference see the change. If this is not the intended effect, it can be difficult to notify the other users to have them respond correctly. In these situations, defensive copying of the entire object rather than the reference is usually an easy but costly solution. The observer pattern is an alternative technique for handling changes to mutable objects.

Copy-on-write

[edit]

A technique that blends the advantages of mutable and immutable objects, and is supported directly in almost all modern hardware, is copy-on-write (COW). Using this technique, when a user asks the system to copy an object, it instead merely creates a new reference that still points to the same object. As soon as a user attempts to modify the object through a particular reference, the system makes a real copy, applies the modification to that, and sets the reference to refer to the new copy. The other users are unaffected, because they still refer to the original object. Therefore, under COW, all users appear to have a mutable version of their objects, although in the case that users do not modify their objects, the space-saving and speed advantages of immutable objects are preserved. Copy-on-write is popular in virtual memory systems because it allows them to save memory space while still correctly handling anything an application program might do.

Interning

[edit]

The practice of always using references in place of copies of equal objects is known as interning. If interning is used, two objects are considered equal if and only if their references, typically represented as pointers or integers, are equal. Some languages do this automatically: for example, Python automatically interns short strings. If the algorithm that implements interning is guaranteed to do so in every case that it is possible, then comparing objects for equality is reduced to comparing their pointers – a substantial gain in speed in most applications. (Even if the algorithm is not guaranteed to be comprehensive, there still exists the possibility of a fast path case improvement when the objects are equal and use the same reference.) Interning is generally only useful for immutable objects.

Thread safety

[edit]

Immutable objects can be useful in multi-threaded applications. Multiple threads can act on data represented by immutable objects without concern of the data being changed by other threads. Immutable objects are therefore considered more thread-safe than mutable objects.

Violating immutability

[edit]

Immutability does not imply that the object as stored in the computer's memory is unwriteable. Rather, immutability is a compile-time construct that indicates what a programmer can do through the normal interface of the object, not necessarily what they can absolutely do (for instance, by circumventing the type system or violating const correctness in C or C++).

Language-specific details

[edit]

In Python, Java[5]: 80  and the .NET Framework, strings are immutable objects. Both Java and the .NET Framework have mutable versions of string. In Java[5]: 84  these are StringBuffer and StringBuilder (mutable versions of Java String) and in .NET this is StringBuilder (mutable version of .Net String). Python 3 has a mutable string (bytes) variant, named bytearray.[6]

Additionally, all of the primitive wrapper classes in Java are immutable.

Similar patterns are the Immutable Interface and Immutable Wrapper.

In pure functional programming languages it is not possible to create mutable objects without extending the language (e.g. via a mutable references library or a foreign function interface), so all objects are immutable.

Ada

[edit]

In Ada, any object is declared either variable (i.e. mutable; typically the implicit default), or constant (i.e. immutable) via the constant keyword.

  type Some_type is new Integer; -- could be anything more complicated
  x: constant Some_type:= 1; -- immutable
  y: Some_type; -- mutable

Subprogram parameters are immutable in the in mode, and mutable in the in out and out modes.

  procedure Do_it(a: in Integer; b: in out Integer; c: out Integer) is
  begin
    -- a is immutable
    b:= b + a;
    c:= a;
  end Do_it;

C#

[edit]

In C# you can enforce immutability of the fields of a class with the readonly statement.[7]: 239  By enforcing all the fields as immutable, you obtain an immutable type.

class AnImmutableType
{
    public readonly double _value;
    public AnImmutableType(double x) 
    { 
        _value = x; 
    }
    public AnImmutableType Square() 
    { 
        return new AnImmutableType(_value * _value); 
    }
}

C# have records which are immutable.[8][9]

record Person(string FirstName, string LastName);

C++

[edit]

In C++, a const-correct implementation of ShoppingCart would allow the user to create instances of the class and then use them as either const (immutable) or mutable, as desired, by providing two different versions of the items() method. (Notice that in C++ it is not necessary — and in fact impossible — to provide a specialized constructor for const instances.)

using std::vector;
using std::views::transform;

class ShoppingCart {
private:
    vector<Merchandise> items;
public:
    explicit Cart(const vector<Merchandise>& items): 
        items{items} {}

    vector<Merchandise>& items() { 
        return items; 
    }

    const vector<Merchandise>& items() const { 
        return items;
    }

    double computeTotalCost() const { 
        return std::ranges::accumulate(
            items | transform([](const Merchandise& m) -> double { return m.getPrice(); }), 
            0.0
        );
    }
};

Note that, when there is a data member that is a pointer or reference to another object, then it is possible to mutate the object pointed to or referenced only within a non-const method.

C++ also provides abstract (as opposed to bitwise) immutability via the mutable keyword, which lets a member variable be changed from within a const method.

using std::optional;
using std::vector;
using std::views::transform;

class ShoppingCart {
private:
    vector<Merchandise> items;
    mutable optional<int> totalCost;
public:
    explicit Cart(const vector<Merchandise>& items): 
        items{items} {}

    const vector<Merchandise>& items() const { 
        return items; 
    }

    int computeTotalCost() const {
        if (!totalCost) {
            totalCost = std::ranges::accumulate(
                items | transform([](const Merchandise& m) -> double { return m.getPrice(); }), 
                0.0
            );
        }
        return *totalCost;
    }
};

D

[edit]

In D, there exist two type qualifiers, const and immutable, for variables that cannot be changed.[10] Unlike C++'s const, Java's final, and C#'s readonly, they are transitive and recursively apply to anything reachable through references of such a variable. The difference between const and immutable is what they apply to: const is a property of the variable: there might legally exist mutable references to referred value, i.e. the value can actually change. In contrast, immutable is a property of the referred value: the value and anything transitively reachable from it cannot change (without breaking the type system, leading to undefined behavior). Any reference of that value must be marked const or immutable. Basically for any unqualified type T, const(T) is the disjoint union of T (mutable) and immutable(T).

class C {
  /*mutable*/ Object mField;
    const     Object cField;
    immutable Object iField;
}

For a mutable C object, its mField can be written to. For a const(C) object, mField cannot be modified, it inherits const; iField is still immutable as it is the stronger guarantee. For an immutable(C), all fields are immutable.

In a function like this:

void func(C m, const C c, immutable C i)
{ /* inside the braces */ }

Inside the braces, c might refer to the same object as m, so mutations to m could indirectly change c as well. Also, c might refer to the same object as i, but since the value then is immutable, there are no changes. However, m and i cannot legally refer to the same object.

In the language of guarantees, mutable has no guarantees (the function might change the object), const is an outward-only guarantee that the function will not change anything, and immutable is a bidirectional guarantee (the function will not change the value and the caller must not change it).

Values that are const or immutable must be initialized by direct assignment at the point of declaration or by a constructor.

Because const parameters forget if the value was mutable or not, a similar construct, inout, acts, in a sense, as a variable for mutability information. A function of type const(S) function(const(T)) returns const(S) typed values for mutable, const and immutable arguments. In contrast, a function of type inout(S) function(inout(T)) returns S for mutable T arguments, const(S) for const(T) values, and immutable(S) for immutable(T) values.

Casting immutable values to mutable inflicts undefined behavior upon change, even if the original value comes from a mutable origin. Casting mutable values to immutable can be legal when there remain no mutable references afterward. "An expression may be converted from mutable (...) to immutable if the expression is unique and all expressions it transitively refers to are either unique or immutable."[10] If the compiler cannot prove uniqueness, the casting can be done explicitly and it is up to the programmer to ensure that no mutable references exist.

The type string is an alias for immutable(char)[], i.e. a typed slice of memory of immutable characters.[11] Making substrings is cheap, as it just copies and modifies a pointer and a length filed, and safe, as the underlying data cannot be changed. Objects of type const(char)[] can refer to strings, but also to mutable buffers.

Making a shallow copy of a const or immutable value removes the outer layer of immutability: Copying an immutable string (immutable(char[])) returns a string (immutable(char)[]). The immutable pointer and length are being copied and the copies are mutable. The referred data has not been copied and keeps its qualifier, in the example immutable. It can be stripped by making a depper copy, e.g. using the dup function.

Java

[edit]

A classic example of an immutable object is an instance of the Java String class

String s = "ABC";
s.toLowerCase(); // This accomplishes nothing!

The method toLowerCase() does not change the data "ABC" that s contains. Instead, a new String object is instantiated and given the data "abc" during its construction. A reference to this String object is returned by the toLowerCase() method. To make the String s contain the data "abc", a different approach is needed:

s = s.toLowerCase();

Now the String s references a new String object that contains "abc". There is nothing in the syntax of the declaration of the class String that enforces it as immutable; rather, none of the String class's methods ever affect the data that a String object contains, thus making it immutable.

The keyword final (detailed article) is used in implementing immutable primitive types and object references,[12] but it cannot, by itself, make the objects themselves immutable. See below examples:

Primitive type variables (int, long, short, etc.) can be reassigned after being defined. This can be prevented by using final.

int i = 42; //int is a primitive type
i = 43; // OK

final int j = 42;
j = 43; // does not compile. j is final so can't be reassigned

Reference types cannot be made immutable just by using the final keyword. final only prevents reassignment.

final MyObject m = new MyObject(); //m is of reference type
m.data = 100; // OK. We can change state of object m (m is mutable and final doesn't change this fact)
m = new MyObject(); // does not compile. m is final so can't be reassigned

Primitive wrappers (Integer, Long, Short, Double, Float, Character, Byte, Boolean) are also all immutable. Immutable classes can be implemented by following a few simple guidelines.[13]

JavaScript

[edit]

In JavaScript, all primitive types (Undefined, Null, Boolean, Number, BigInt, String, Symbol) are immutable, but custom objects are generally mutable.

function doSomething(x) { /* does changing x here change the original? */ };
var str = 'a string';
var obj = { an: 'object' };
doSomething(str);         // strings, numbers and bool types are immutable, function gets a copy
doSomething(obj);         // objects are passed in by reference and are mutable inside function
doAnotherThing(str, obj); // `str` has not changed, but `obj` may have.

To simulate immutability in an object, one may define properties as read-only (writable: false).

var obj = {};
Object.defineProperty(obj, 'foo', { value: 'bar', writable: false });
obj.foo = 'bar2'; // silently ignored

However, the approach above still lets new properties be added. Alternatively, one may use Object.freeze to make existing objects immutable.

var obj = { foo: 'bar' };
Object.freeze(obj);
obj.foo = 'bars'; // cannot edit property, silently ignored
obj.foo2 = 'bar2'; // cannot add property, silently ignored

With the implementation of ECMA262, JavaScript has the ability to create immutable references that cannot be reassigned. However, using a const declaration doesn't mean that value of the read-only reference is immutable, just that the name cannot be assigned to a new value.

const ALWAYS_IMMUTABLE = true;

try {
  ALWAYS_IMMUTABLE = false;
} catch (err) {
  console.log("Can't reassign an immutable reference.");
}

const arr = [1, 2, 3];
arr.push(4);
console.log(arr); // [1, 2, 3, 4]

The use of immutable state has become a rising trend in JavaScript since the introduction of React, which favours Flux-like state management patterns such as Redux.[14]

Perl

[edit]

In Perl, one can create an immutable class with the Moo library by simply declaring all the attributes read only:

package Immutable;
use Moo;

has value => (
    is      => 'ro',   # read only
    default => 'data', # can be overridden by supplying the constructor with
                       # a value: Immutable->new(value => 'something else');
);

1;

Creating an immutable class used to require two steps: first, creating accessors (either automatically or manually) that prevent modification of object attributes, and secondly, preventing direct modification of the instance data of instances of that class (this was usually stored in a hash reference, and could be locked with Hash::Util's lock_hash function):

package Immutable;
use strict;
use warnings;
use base qw(Class::Accessor);
# create read-only accessors
__PACKAGE__->mk_ro_accessors(qw(value));
use Hash::Util 'lock_hash';

sub new {
    my $class = shift;
    return $class if ref($class);
    die "Arguments to new must be key => value pairs\n"
        unless (@_ % 2 == 0);
    my %defaults = (
        value => 'data',
    );
    my $obj = {
        %defaults,
        @_,
    };
    bless $obj, $class;
    # prevent modification of the object data
    lock_hash %$obj;
}
1;

Or, with a manually written accessor:

package Immutable;
use strict;
use warnings;
use Hash::Util 'lock_hash';

sub new {
    my $class = shift;
    return $class if ref($class);
    die "Arguments to new must be key => value pairs\n"
        unless (@_ % 2 == 0);
    my %defaults = (
        value => 'data',
    );
    my $obj = {
        %defaults,
        @_,
    };
    bless $obj, $class;
    # prevent modification of the object data
    lock_hash %$obj;
}

# read-only accessor
sub value {
    my $self = shift;
    if (my $new_value = shift) {
        # trying to set a new value
        die "This object cannot be modified\n";
    } else {
        return $self->{value}
    }
}
1;

PHP

[edit]

In PHP have readonly properties since version 8.1 and readonly classes since version 8.2.[15][16]

readonly class BlogData
{
    public string $title;

    public Status $status;

    public function __construct(string $title, Status $status)
    {
        $this->title = $title;
        $this->status = $status;
    }
}

Python

[edit]

In Python, some built-in types (numbers, Booleans, strings, tuples, frozensets) are immutable, but custom classes are generally mutable. To simulate immutability in a class, one could override attribute setting and deletion to raise exceptions:

class ImmutablePoint:
    """An immutable class with two attributes 'x' and 'y'."""

    __slots__ = ["x", "y"]

    def __setattr__(self, *args):
        raise TypeError("Can not modify immutable instance.")

    __delattr__ = __setattr__

    def __init__(self, x, y):
        # We can no longer use self.value = value to store the instance data
        # so we must explicitly call the superclass
        super().__setattr__("x", x)
        super().__setattr__("y", y)

The standard library helpers collections.namedtuple and typing.NamedTuple, available from Python 3.6 onward, create simple immutable classes. The following example is roughly equivalent to the above, plus some tuple-like features:

from typing import NamedTuple
import collections

Point = collections.namedtuple("Point", ["x", "y"])

# the following creates a similar namedtuple to the above
class Point(NamedTuple):
    x: int
    y: int

Introduced in Python 3.7, dataclasses allow developers to emulate immutability with frozen instances. If a frozen dataclass is built, dataclasses will override __setattr__() and __delattr__() to raise FrozenInstanceError if invoked.

from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

Racket

[edit]

Racket substantially diverges from other Scheme implementations by making its core pair type ("cons cells") immutable. Instead, it provides a parallel mutable pair type, via mcons, mcar, set-mcar! etc. In addition, many immutable types are supported, for example, immutable strings and vectors, and these are used extensively. New structs are immutable by default, unless a field is specifically declared mutable, or the whole struct:

(struct foo1 (x y))             ; all fields immutable
(struct foo2 (x [y #:mutable])) ; one mutable field
(struct foo3 (x y) #:mutable)   ; all fields mutable

The language also supports immutable hash tables, implemented functionally, and immutable dictionaries.

Rust

[edit]

Rust's ownership system allows developers to declare immutable variables, and pass immutable references. By default, all variables and references are immutable. Mutable variables and references are explicitly created with the mut keyword.

Constant items in Rust are always immutable.

// constant items are always immutable
const ALWAYS_IMMUTABLE: bool = true;

struct Object {
    x: usize,
    y: usize,
}

fn main() {
    // explicitly declare a mutable variable
    let mut mutable_obj = Object { x: 1, y: 2 };
    mutable_obj.x = 3; // okay

    let mutable_ref = &mut mutable_obj;
    mutable_ref.x = 1; // okay

    let immutable_ref = &mutable_obj;
    immutable_ref.x = 3; // error E0594

    // by default, variables are immutable
    let immutable_obj = Object { x: 4, y: 5 };
    immutable_obj.x = 6; // error E0596

    let mutable_ref2 = 
        &mut immutable_obj; // error E0596

    let immutable_ref2 = &immutable_obj;
    immutable_ref2.x = 6; // error E0594
    
}

Scala

[edit]

In Scala, any entity (narrowly, a binding) can be defined as mutable or immutable: in the declaration, one can use val (value) for immutable entities and var (variable) for mutable ones. Note that even though an immutable binding can not be reassigned, it may still refer to a mutable object and it is still possible to call mutating methods on that object: the binding is immutable, but the underlying object may be mutable.

For example, the following code snippet:

val maxValue = 100
var currentValue = 1

defines an immutable entity maxValue (the integer type is inferred at compile-time) and a mutable entity named currentValue.

By default, collection classes such as List and Map are immutable, so update-methods return a new instance rather than mutating an existing one. While this may sound inefficient, the implementation of these classes and their guarantees of immutability mean that the new instance can re-use existing nodes, which, especially in the case of creating copies, is very efficient.[17][better source needed]

See also

[edit]

References

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
An immutable object is an object in object-oriented and whose internal state cannot be modified after it is fully constructed, ensuring that any apparent changes result in the creation of a new object rather than altering the original. This property distinguishes immutable objects from mutable ones, which allow in-place modifications to their data or attributes. Immutable objects provide significant benefits in , particularly in multithreaded environments, where they prevent and race conditions by eliminating shared mutable state. They also enhance code predictability and maintainability, as developers can reason about program behavior without tracking potential side effects from modifications, a central to paradigms. Additionally, immutability can improve performance through optimizations like memory sharing and reduced synchronization overhead, as seen in the design of immutable strings in languages like Python. Common examples of immutable objects appear across programming languages. In , the String class is inherently immutable, supporting and efficient for duplicate values. Python's built-in types, including integers, floating-point numbers, strings, and tuples, are immutable, promoting safer data handling in sequences and promoting functional-style operations. Similarly, .NET provides immutable collections like ImmutableList, which use persistent data structures to enable efficient updates while preserving original instances.

Fundamentals

Definition and Basic Principles

An immutable object is a data structure whose state cannot be modified after it is created; any operation that purports to alter it instead produces a new object with the updated state. This principle contrasts with mutable objects, where internal fields or elements can be directly changed in place. Immutability is a foundational concept in computer science, particularly in paradigms that emphasize predictability and safety in data handling. The concept of immutable objects gained prominence in the late 1950s and early 1960s through the development of functional programming languages, notably Lisp, created by John McCarthy. In his seminal 1960 paper, McCarthy introduced symbolic expressions (S-expressions) in Lisp—atomic symbols or ordered pairs—manipulated via functional composition and recursion without side effects in the presented model, laying groundwork for immutability in programming languages. However, later Lisp implementations introduced mechanisms for mutating list structures, such as rplaca and rplacd, though the functional paradigm encouraged immutability. This approach formalized the use of recursion without mutation in the theoretical design, enabling computations on shared substructures. To illustrate, consider pseudocode for an immutable integer and a mutable list: Immutable Integer Example:

let x = 5 // Creates immutable integer object let y = x + 3 // Creates new immutable integer 8; x remains 5

let x = 5 // Creates immutable integer object let y = x + 3 // Creates new immutable integer 8; x remains 5

Mutable List Example (for contrast in principle):

let lst = [1, 2, 3] // Creates mutable list lst.append(4) // Modifies lst in place to [1, 2, 3, 4]

let lst = [1, 2, 3] // Creates mutable list lst.append(4) // Modifies lst in place to [1, 2, 3, 4]

In the immutable case, "updates" involve reassignment to new objects, preserving the original. This design supports key principles like referential transparency, where an expression can be substituted with its evaluated value without altering the program's behavior, as ensured by the absence of side effects in pure functions.

Mutable vs. Immutable Objects

Mutable objects allow in-place modifications to their internal state, typically through methods that directly alter fields or elements, which can introduce side effects that propagate through the program. For instance, a method like setValue() on a mutable object updates its state immediately, enabling efficient reuse but risking unintended changes if the object is accessed via multiple references. In contrast, immutable objects prohibit any post-creation modifications; operations that appear to change the object instead return a new instance with the updated state, preserving the original object's and avoiding side effects. The primary advantage of immutability lies in reducing bugs from unintended state alterations, as the object's remain constant, facilitating easier code reasoning and verification of program behavior. This predictability enhances overall program correctness by minimizing issues like race conditions in concurrent environments, where shared mutable state could otherwise lead to inconsistencies. Immutable designs also promote safer sharing of objects across components, as there is no possibility of external modifications corrupting the data. Mutability, while offering simplicity for frequent updates without allocation overhead, carries drawbacks such as increased susceptibility to errors, where changes via one reference unexpectedly affect others pointing to the same object. These errors can complicate and maintenance, as the non-local effects of modifications make it harder to trace program flow and ensure reliability. To illustrate the differences, consider a scenario involving collection updates: with a mutable , appending an element modifies the existing structure directly, potentially altering its length and contents in place with minimal allocation but exposing it to side-effect risks if aliased. In an immutable , the same operation concatenates to create an entirely new list, leaving the original intact, which avoids aliasing issues but may incur higher memory usage from repeated object creation unless optimized. The table below compares key implications:
AspectMutable ObjectsImmutable Objects
State ModificationIn-place changes (e.g., via append() or set())New object creation for updates
Side EffectsHigh risk; modifications affect all referencesNone; originals remain unchanged
Memory EfficiencyLower overhead for repeated updates (reuse structure)Higher due to copies, but enables and optimizations like interning
Error PronenessProne to and unintended mods, increasing bugsReduced bugs; simplifies reasoning and testing
Aliasing concerns with mutable objects are particularly pronounced, as multiple references can lead to subtle errors that immutability inherently mitigates by design.

Core Concepts

Immutability in Variables and References

Immutable variables bind to a value that cannot be rebound or altered after initialization, enforcing a fixed association between the variable name and its . In languages like , the final keyword declares such variables, preventing reassignment while allowing mutation of mutable objects they reference unless further restrictions are applied. This mechanism promotes predictable behavior by isolating state changes, though it does not inherently guarantee object-level immutability. Similarly, in , immutability is the default for variable bindings, with explicit mut annotations required for modification, leveraging rules to prevent during mutable access. References to immutable objects allow multiple variables or pointers to point to the same unchanging entity in memory, enabling efficient sharing without risk of unintended modifications through any reference. Reassigning such a reference creates a new binding to a different object, leaving the original immutable object intact and unaffected. For instance, in Python, variables serve as references to immutable objects like strings or tuples; assignment merely updates the reference without copying the object, as the value cannot be altered post-creation. This approach contrasts with mutable references, where changes propagate across all aliases, but for immutable cases, it ensures . In the Javari extension to , type annotations distinguish immutable references that prohibit modification of the object's transitive state, supporting safe sharing in object-oriented contexts. Immutable objects preserve a consistent object identity, where equality is determined by content rather than , and hash codes remain stable throughout the object's lifetime since no state changes occur. In , the equals method for immutable classes like String compares values, and the corresponding hashCode method yields the same result for equal instances, adhering to the that equal objects must share hash codes to support reliable use in hash-based collections. This content-based identity avoids issues arising from mutable state, ensuring that an object's hash code does not vary if its value does not. Python enforces similar consistency for hashable immutable types, requiring __hash__ implementations to be invariant with respect to __eq__, preventing errors in sets or dictionaries. In heap-based memory models, immutable objects are allocated once in the heap and referenced by multiple variables, forming a directed reference graph where nodes represent objects and edges denote . This structure facilitates garbage collection by identifying reachable immutable objects without mutation concerns, as seen in systems like the or Python's interpreter. The following simple diagram illustrates a reference graph for immutable objects:

Variable A ───┐ ├── Immutable Object X (heap) Variable B ───┘ │ ├── Field: Immutable Value Y └── Field: [Reference](/page/Reference) to Immutable Object Z Variable C ──────────────────────┘ (also [references](/page/Reference) Z)

Variable A ───┐ ├── Immutable Object X (heap) Variable B ───┘ │ ├── Field: Immutable Value Y └── Field: [Reference](/page/Reference) to Immutable Object Z Variable C ──────────────────────┘ (also [references](/page/Reference) Z)

Here, reassigning Variable A to a new object creates a separate path in the graph, without altering X or Z, promoting memory efficiency through shared immutable structures.

Strong vs. Weak Immutability

immutability enforces that an object cannot be modified through any access path, with guarantees provided by the language's to prevent all forms of , including internal changes. This level of enforcement often relies on compile-time checks, such as const-correctness extended transitively across all reachable components or sealed classes that prohibit subclassing and ensure final fields remain unchanged. In practice, strong immutability supports pure functions by ensuring inputs and outputs are unaltered, enabling reliable reasoning about program behavior without side effects. Weak immutability, in contrast, permits internal mutations but exposes only immutable views to external code, typically through mechanisms like defensive copying in accessors or read-only references that do not guarantee global non-modifiability. For instance, a method might return a shallow copy of an internal mutable structure, allowing the object's state to change behind the scenes while preventing direct external alterations. This approach balances flexibility with partial safety, often used where performance constraints make full immutability impractical. The distinction between strong and weak immutability has evolved alongside programming paradigms. Strong immutability emerged in the type systems of ML-family functional languages during the 1970s, where immutable values became the default to support and concurrency safety in systems like LCF's Meta Language (1973–1978). Weak immutability gained prominence in object-oriented languages like (introduced 1995), relying on runtime conventions and patterns rather than strict type enforcement to achieve practical immutability in mutable environments.
CriterionStrong ImmutabilityWeak Immutability
Enforcement MechanismCompile-time type system guarantees (e.g., transitive const or sealed types); no mutable paths exist.Runtime or alias-specific restrictions (e.g., defensive copies or read-only views); mutations possible via other paths.
Use CasesPure functions, formal verification, and high-assurance concurrency where absolute non-modification is required.Performance-sensitive scenarios like caching or legacy integration, providing immutability illusions without full overhead.
Pseudocode Examplepseudocode<br>sealed class Point(val x: Int, val y: Int) // No subclassing, final fields<br> def distance(other: Point): Double = math.sqrt((x - other.x)^2 + (y - other.y)^2)<br>// Type system ensures Point instances never mutate<br>pseudocode<br>class Container(private var data: Array[Int])<br> def getData: Array[Int] = data.clone // Defensive copy<br> private def updateInternal(newData: Array[Int]): Unit = data = newData // Internal mutation allowed<br>// External views are immutable, but object can change internally<br>

Aliasing and Object Identity

Aliasing occurs when multiple variables or references point to the same underlying object in , creating multiple names for the same . In programming languages, this is common with objects, where assigning one reference to another (e.g., z = x) results in both variables sharing the object's identity without copying its contents. With mutable objects, introduces risks such as "mutation at a distance," where changes through one reference unexpectedly alter the object observed via another, leading to bugs that are difficult to trace. For instance, modifying a list via one alias can silently affect all aliases, causing inconsistent program state. In concurrent environments, this exacerbates issues like race conditions, where unsynchronized access to shared mutable state by multiple threads results in unpredictable outcomes. Immutability mitigates these problems by prohibiting modifications after creation, ensuring that aliased references always observe the same unchanging value without side effects or coordination needs. Immutable objects preserve identity through value-based equality, where comparison operators (e.g., ==) evaluate structural content rather than addresses or pointer equality, distinguishing them from mutable objects that often rely on reference identity. This approach avoids pitfalls of pointer-based comparisons, such as false inequalities for semantically equivalent but separately allocated objects. In languages with garbage collection, safe of immutable objects enables compiler and runtime optimizations, including , where repeated computations yielding the same immutable value can be reused without recomputation. Interning serves as one such technique, where identical immutable values share a single representation to further enhance efficiency.

Implementation Techniques

Copy-on-Write Optimization

Copy-on-write (CoW) is a resource-management optimization technique that enables efficient sharing of mutable data structures across multiple references by deferring the actual copying of data until a write operation is required. In this approach, initially shared copies are marked as read-only, allowing multiple entities to access the same underlying data without immediate duplication costs; upon a modification attempt, the system creates a private writable copy for the modifying entity while leaving other references intact. This mechanism is foundational for implementing immutability in scenarios where read operations dominate, as it supports the creation of lightweight, immutable views of potentially mutable data without upfront full copies. The technique finds application in various domains, including operating systems and specialized data structures. In systems, CoW is employed during process forking to share the parent's pages between parent and child processes until a write occurs, avoiding the overhead of duplicating the entire immediately. Similarly, in data structures like ropes—binary trees representing concatenated strings—CoW allows efficient and operations by sharing immutable subtrees; modifications copy only the affected path in the tree, typically O(log n) nodes, preserving immutability for unchanged parts. A typical for CoW in a simple -like structure involves to track sharing. Consider for updating an element in a shared :

function update([array](/page/Array)_ref, index, new_value): if [array](/page/Array)_ref.reference_count > 1: new_[array](/page/Array) = copy([array](/page/Array)_ref.[data](/page/Data)) [array](/page/Array)_ref.[data](/page/Data) = new_[array](/page/Array) [array](/page/Array)_ref.reference_count = 1 // Detach from shared [array](/page/Array)_ref.[data](/page/Data)[index] = new_value

function update([array](/page/Array)_ref, index, new_value): if [array](/page/Array)_ref.reference_count > 1: new_[array](/page/Array) = copy([array](/page/Array)_ref.[data](/page/Data)) [array](/page/Array)_ref.[data](/page/Data) = new_[array](/page/Array) [array](/page/Array)_ref.reference_count = 1 // Detach from shared [array](/page/Array)_ref.[data](/page/Data)[index] = new_value

Here, the reference count determines if sharing exists; if shared, a deep copy is made before , ensuring isolation. This outline mirrors implementations in systems supporting CoW for inheritance. Historically, CoW emerged in the context of Unix process creation with the fork() system call, introduced in the early 1970s, but full CoW optimizations for memory management were developed in the 1980s to address performance bottlenecks in virtual memory systems. These ideas were later extended to user-level programming, notably in C++ through reference-counted smart pointers like those in the Boost library (introduced around 2001), which facilitated CoW idioms for custom immutable types by enabling shared ownership with lazy copying on mutation. For immutability, CoW provides significant benefits by allowing inexpensive creation of immutable snapshots or views from mutable bases, as reads can leverage shared structures without allocation, while writes trigger targeted copies to maintain isolation—ideal for scenarios like versioned data or functional updates where most operations are non-modifying. In concurrent environments, this yields gains by minimizing and copy overhead in read-intensive workloads.

Interning and Shared Structures

Interning is a optimization technique applied to immutable objects, where a maintains a centralized pool of unique instances for identical values, ensuring that subsequent creations reuse existing objects rather than allocating new ones. This process typically employs a for efficient lookups: upon creating an immutable object, such as a , the computes its hash and checks the pool for a matching entry based on equality; if found, the existing reference is returned, otherwise the new object is inserted into the pool. The requirement for immutability is critical, as it prevents any shared instance from being modified, which could affect all aliases unexpectedly. In practice, interning is implemented differently across languages but follows this core mechanism. For instance, in , the String class automatically interns all string literals into a private, fixed-size pool during class loading, while the explicit intern() method allows runtime interning of other strings by returning a canonical representation from the pool. Similarly, Python's sys.intern() function manually interns strings into a global dictionary-like table to accelerate operations like dictionary key lookups, where equal keys must resolve to the same object for hashing consistency. These implementations use hash-based structures to balance lookup speed and memory usage, with pools often garbage-collectable in modern virtual machines to reclaim unused entries. The origins of interning trace back to , introduced around 1958 for handling symbols as unique atomic entities. In early systems, symbols were stored with a single association list per name, ensuring uniqueness via direct address comparison with the eq predicate, which laid the foundation for efficient symbolic computation in applications. This approach has since become common in modern virtual machines for primitives like small integers and strings, promoting shared structures without the risks associated with mutability. By facilitating object reuse, interning significantly reduces memory consumption in scenarios with high duplication, such as parsing repeated identifiers or constants, and accelerates equality testing through comparison rather than content evaluation. For example, in , interned strings allow the == operator to perform pointer equality checks instantaneously, bypassing the more expensive equals() method. This efficiency extends the benefits of to immutable objects, where multiple safely point to the same instance, enhancing overall program without introducing concurrency issues.

Persistent Data Structures

Persistent data structures are immutable data structures designed to support non-destructive updates, where modifications produce new versions of the structure while preserving all previous versions through structural sharing of unchanged components. This approach ensures that updates do not alter existing data, maintaining immutability while achieving efficiency comparable to mutable counterparts in many cases. The core mechanism involves creating new nodes only for the affected parts, allowing multiple versions to coexist by referencing shared substructures. A fundamental concept in persistent data structures is versioning via root pointers: each update operation returns a new root that points to the modified structure, while the original root remains valid and unchanged, enabling access to historical states without additional storage for unmodified elements. This versioning supports applications requiring audit trails or reversible computations, such as systems or environments. Immutability is enforced because nodes are never modified in place; instead, new paths or nodes are constructed as needed. One foundational technique for achieving persistence is path copying, commonly applied to tree-based structures like s. In path copying, an update copies only the path from the to the modified node, sharing the unchanged subtrees with the original version. For example, inserting a key into a involves creating new nodes along the insertion path while reusing existing left and right subtrees where applicable. To illustrate structural sharing in a update, consider a simple before and after inserting a new value. The original tree (version 1) has node A with left child B and right child C. Inserting into the left subtree of B creates a new path: a new A' points to a new B' (with the updated left child), but B's right subtree and C remain shared.

Version 1 (Original): A / \ B C / \ D E Version 2 (After insert under B's left): A' / \ B' C (shared) / \ D' E (shared) / F

Version 1 (Original): A / \ B C / \ D E Version 2 (After insert under B's left): A' / \ B' C (shared) / \ D' E (shared) / F

Here, nodes C, E, and D (if unchanged) are shared between versions, minimizing space overhead to O(log n) for balanced trees. This technique, a building block extending principles, ensures O(log n) time and space per update. Persistent lists can be implemented using cells, as in , where each cons operation creates a new cell pointing to the existing , naturally without . This results in a persistent singly-linked list where appending or prepending yields a new list sharing all but the new head cell. For more complex sequences, finger trees provide a versatile persistent supporting efficient access and modification at both ends in amortized O(1) time, using a spine of nodes with measured subtrees to balance operations. Finger trees generalize deques and sequences through monoidal annotations, enabling applications like priority queues or random-access lists. The development of persistent data structures originated in paradigms, with significant advancements in the late 20th century. Chris Okasaki's seminal work, "Purely Functional Data Structures" (1998), systematized techniques for amortized and worst-case efficient persistence, influencing implementations in languages like . Earlier foundations trace to papers on making mutable structures persistent, such as those by Sleator and Tarjan in 1985.

Benefits and Trade-offs

Thread Safety and Concurrency

Immutable objects inherently provide in concurrent environments because their state cannot be altered after creation, eliminating the possibility of race conditions during simultaneous reads by multiple threads. Without mutable shared state, threads can access and share these objects freely without requiring synchronization primitives such as locks or mutexes, which would otherwise introduce contention and potential deadlocks. This property stems from the absence of interfering writes, allowing developers to focus on logic rather than coordinating access. In practice, passing immutable data structures between threads in concurrent programs prevents race conditions that plague mutable alternatives. For instance, a thread producing an immutable list can safely share it with consumer threads for parallel processing, as modifications would require creating new objects rather than altering the original. This contrasts sharply with mutable objects, where synchronized blocks or atomic operations are necessary to protect shared state, often leading to performance bottlenecks from lock acquisition and release. By design, immutability shifts the burden from runtime to compile-time or structural guarantees. An advanced application appears in the , where Erlang—developed in the —relies on immutable messages for to ensure . Each (lightweight process) maintains isolated state and exchanges only unmodifiable messages via asynchronous passing, avoiding any shared mutable data and thus eliminating the need for locks across distributed systems. This isolation enables fault-tolerant concurrency, with processes handling messages sequentially in their private context. Immutability further enables lock-free data structures, such as persistent trees or queues, where updates produce new versions without modifying existing ones, allowing concurrent readers to operate without barriers. This eliminates costs entirely for read-heavy workloads, facilitating scalable parallelism in high-throughput applications. In multi-threaded contexts, it also addresses concerns by guaranteeing that multiple to the same object reflect consistent, unchanging values.

Performance Implications

Immutable objects, by design, cannot be modified after creation, which introduces specific performance costs primarily related to memory allocation and garbage collection. Every "update" operation requires creating a new object, leading to increased allocation pressure on the heap. In languages with automatic like , this can result in more frequent garbage collection pauses, as the runtime must reclaim unused objects more often. For instance, naive in loops—where strings are immutable—exhibits quadratic O(n²), as each concatenation copies the entire previous into a new object, accumulating redundant copies. To mitigate these costs, developers employ techniques such as builder patterns or specialized mutable builders that defer immutability until the final object is constructed. In , for example, the StringBuilder class allows efficient in-place modifications during construction, achieving linear O(n) time for building strings, after which the immutable is created only once. Similarly, efficient constructors that initialize immutable objects in a single allocation step can reduce overhead compared to incremental updates. These approaches balance the safety of immutability with the efficiency of mutability during transient phases. On the benefits side, immutable objects enhance cache efficiency because their contents never change, avoiding cache invalidations that occur with mutable data. This predictability allows data to remain resident in CPU caches longer, improving access times for repeated reads. Additionally, compilers can apply aggressive optimizations to immutable code, such as , where expressions involving immutable values are evaluated at rather than runtime, reducing execution overhead. In the JVM, immutability enables further optimizations like eliding unnecessary checks for final fields. Empirical studies highlight the trade-offs, showing typical overheads in object-oriented languages but potential gains in functional paradigms through shared structures. Techniques like further reduce costs by sharing unmodified portions of data structures across versions.

Enforcing and Violating Immutability

Enforcing immutability in programming languages often relies on language features and design patterns that prevent state changes after object creation. In languages like , declaring fields as final ensures they cannot be reassigned once initialized, providing a foundational mechanism for immutability by guaranteeing that the object's state remains constant throughout its lifetime. Private constructors further support this by restricting instantiation to controlled factory methods or builders, which can enforce validation and prevent direct access that might lead to mutable instances. Type systems can enhance enforcement through annotations; for instance, Project Lombok's @Value annotation automatically generates immutable classes with private final fields, getters, and no setters, simplifying the creation of thread-safe objects without . Despite these safeguards, immutability can be violated through various mechanisms, often intentionally for debugging or unintentionally due to oversight. Reflection APIs allow bypassing access modifiers, enabling modification of private final fields in supposedly immutable objects, which undermines the intended guarantees and can introduce subtle bugs. A common accidental violation occurs when an immutable class exposes mutable inner objects, such as returning a modifiable array or list from a getter method, allowing external code to alter the object's internal state indirectly. Unsafe casts, particularly in systems with weak type safety, can convert read-only references to mutable ones, leading to unauthorized modifications that violate the immutability contract. Detecting such violations requires static analysis tools that inspect code for potential breaches. SpotBugs, the successor to FindBugs, performs bytecode analysis to identify issues like exposure of internal representations or improper handling of final fields, helping developers catch immutability flaws early in the development cycle. These violations carry significant risks, including security vulnerabilities where assumed-immutable data is modified, potentially enabling or ; for example, the CWE-471 category documents cases in drivers and systems where altering immutable addresses leads to exploitable conditions.

Language-Specific Implementations

Java and JVM Languages

In , immutability is primarily enforced through the final keyword, which prevents reassignment of variables, fields, and parameters after initialization, and restricts subclassing of classes when applied to the class itself. This mechanism supports the creation of immutable classes by ensuring that instance fields cannot be modified post-construction, as seen in core types like . The class, introduced in JDK 1.0 in 1996, is a foundational immutable type whose instances cannot be altered after creation, promoting safe sharing across threads and reducing memory overhead through an interned string pool that reuses identical string literals via the intern() method. Introduced in Java 8 (2014), the concept of "effectively final" variables extends immutability to local variables used in lambdas and inner classes; these are variables that are never reassigned after initialization, even without explicit final declaration, allowing their capture in functional constructs without risking concurrent modification. Scala, a JVM language emphasizing functional programming, builds on Java's immutability with language-level features like val declarations, which create immutable values akin to Java's final fields but with stricter enforcement and no reassignment possible. Case classes in Scala automatically generate immutable accessor methods for constructor parameters (treated as val fields), along with structural equals, hashCode, and toString implementations, facilitating concise modeling of immutable data without boilerplate. Recent enhancements further streamline immutability. , previewed in Java 14 (2020) via JEP 359, provide a compact syntax for shallowly immutable data carriers with final components, automatically generated accessors, equals, hashCode, and toString, reducing verbosity for plain data classes. Project Valhalla, an ongoing effort, introduces preview value types (e.g., via JEP 401 in early-access builds from 2023–2025) as identity-less, immutable primitives that eliminate object overhead while supporting user-defined types, enhancing performance for numerical and data-oriented applications. To design immutable classes in , developers mark all fields as private final, provide a constructor for initialization, and avoid exposing mutable state; for instance, getters returning collections should use defensive copying or return unmodifiable views to prevent external modification.

java

public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } }

public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } }

When a class must hold mutable collections, best practices recommend returning defensive copies in getters, such as Collections.unmodifiableList(new ArrayList<>(internalList)), to preserve internal immutability without sharing modifiable references. In Scala, case classes inherently follow similar principles, with parameters as immutable vals and encouraging functional, non-mutating usage.

scala

case class Point(x: Int, y: Int) val p = Point(1, 2) // Immutable instance; no setters

case class Point(x: Int, y: Int) val p = Point(1, 2) // Immutable instance; no setters

Python and Dynamic Languages

In Python, several built-in types are inherently immutable, including integers, strings, and tuples, which cannot be modified after creation to ensure predictable behavior and enable optimizations like hashing. The frozenset type provides an immutable alternative to the mutable set, allowing hashable collections of unique elements that remain unchanged once constructed. For creating lightweight, immutable records with named fields, Python offers collections.namedtuple since version 2.6, which subclasses tuples to provide attribute access while preserving immutability. Additionally, since Python 3.7, the dataclasses module introduces the @dataclass(frozen=True) decorator, which generates immutable classes by raising an error on attribute modification attempts, emulating true immutability for user-defined data structures. However, Python's dynamic typing introduces challenges to strict immutability; while the structure of immutable types like tuples cannot change, they can reference mutable objects, allowing indirect modifications that alter the effective value of the container. For instance, a tuple containing a list permits changes to the list's contents, highlighting the limitations of shallow immutability in dynamic environments. Recent updates in Python 3.12 enhance typing support for expressing immutability through improved generic type parameters and type aliases, enabling static type checkers to better enforce immutable hints via constructs like typing.Final. In , a dynamically typed language, immutability is supported through the const keyword introduced in ECMAScript 6 (ES6) in 2015, which prevents reassignment of variables but does not protect against mutation of object properties. The Object.freeze() method provides shallow immutability by preventing additions, deletions, or reconfigurations of an object's properties, though nested objects remain mutable. For deeper, persistent immutability, libraries like Immutable.js offer specialized data structures such as List, Map, and Set, which create new instances on updates rather than modifying existing ones, facilitating efficient handling of complex state in applications. The Records and Tuples proposal, previously at Stage 2, was withdrawn in April 2025 due to performance and implementation challenges in engines and thus not included in ES2025, though its concepts continue to influence discussions on built-in immutability. These features underscore the trade-offs in dynamic languages, where runtime flexibility often requires explicit tools or libraries to achieve robust immutability without compromising .

C++ and Systems Languages

In C++, immutability is enforced primarily through the const type qualifier, introduced in the C++98 standard, which declares objects or references that cannot be modified after initialization. The const qualifier can apply to variables, pointers, and references; for instance, a const int prevents reassignment, while a const member function promises not to alter the object's non-mutable state, enabling safe const-correctness in class designs. This manual approach requires programmers to explicitly propagate const through interfaces to avoid compilation errors when attempting modifications. C++11 extended compile-time immutability with the constexpr specifier, allowing variables, functions, and objects to be evaluated at , ensuring their values are constant and immutable during execution. For example, a constexpr function can compute results used in array sizes or template parameters, promoting optimization and preventing runtime changes. In C++17, std::string_view introduced a lightweight, non-owning view over a contiguous character , providing an immutable reference without copying the underlying data, which is particularly useful for passing strings to functions without transfer. In systems languages like C#, the readonly keyword declares fields that can only be assigned during declaration or in a constructor, enforcing immutability at the instance level after initialization. C# 10 introduced record structs, value types that support immutability by default when declared as readonly record struct, combining structural equality with positional syntax for concise, thread-safe . The System.Collections.Immutable provides specialized collections like ImmutableList<T> and ImmutableDictionary<TKey, TValue>, which return new instances on modification operations, preserving the original data's immutability without shared mutable state. Developers in these languages often manually enforce immutability using smart pointers, such as C++'s std::shared_ptr, which enables shared ownership and can implement (CoW) semantics by delaying copies until mutation, though this requires careful design to avoid pointer where multiple references unexpectedly modify shared data. Recent updates include C++23's enhancements to modules, which improve const propagation in immediate functions via consteval upward propagation (P2564R2), strengthening compile-time guarantees. In C#, features from C# 8 onward, refined in the 2020s with record types, facilitate deconstruction and switching on immutable data for safer, more expressive code.

cpp

// Example: Immutable view in C++ std::string str = "hello"; std::string_view view = str; // Non-owning, immutable reference // view[0] = 'H'; // Compilation error

// Example: Immutable view in C++ std::string str = "hello"; std::string_view view = str; // Non-owning, immutable reference // view[0] = 'H'; // Compilation error

csharp

// Example: Immutable record struct in C# 10 readonly record struct Point(int X, int Y); // Creates an immutable value type with value-based equality

// Example: Immutable record struct in C# 10 readonly record struct Point(int X, int Y); // Creates an immutable value type with value-based equality

Rust and Memory-Safe Languages

In Rust, a systems programming language designed with memory safety in mind, immutability is enforced by default through its ownership model, which tracks the lifetime and mutability of data at compile time. Variables declared with let are immutable, meaning their values cannot be changed after initialization, promoting safer code by preventing unintended modifications. To allow mutation, the mut keyword must be explicitly added, as in let mut x = 5;. Similarly, references created with & are immutable by default and cannot be used to modify the referenced data, while mutable references require &mut. This design choice ensures that data access is explicit and controlled, reducing bugs related to unexpected state changes. Central to Rust's immutability enforcement is the system, where each value has a single owner responsible for its , and the borrow checker—a part of the compiler—validates borrowing rules to prevent issues like data races or invalid memory access. The borrow checker enforces that data cannot be aliased (multiple references existing simultaneously) while being mutated, adhering to the principle that data should not be both aliased and mutated. For instance, multiple immutable borrows (&T) can coexist, but a mutable borrow (&mut T) requires exclusive access, and immutable borrows must outlive any mutable ones to avoid lifetime violations. This compile-time verification catches potential errors early, ensuring without a garbage collector. For duplicating data in a safe manner, provides the Copy and Clone traits, introduced in the language's 1.0 stable release in 2015. The Copy trait enables implicit, bitwise copying for simple types like integers or tuples without heap allocation, but only for types that do not implement Drop to avoid resource leaks. More complex types implement Clone for explicit deep copying via the clone() method, allowing controlled duplication while respecting rules. These traits ensure that immutability is preserved during value transfer, as copies create independent instances without shared mutable state. Rust also supports interior mutability for scenarios where mutation is needed despite immutable outer references, using types like Rc<RefCell<T>> to enable shared and runtime-checked borrowing. Rc () provides multiple immutable pointers to the same data, while RefCell wraps the inner value and enforces borrowing rules dynamically via panics on violations, rather than at . This pattern is a controlled exception to strict immutability, useful for single-threaded code like mock objects in tests, but it introduces runtime overhead and potential panics if rules are breached. For example:

rust

use std::rc::Rc; use std::cell::RefCell; let data = Rc::new(RefCell::new(5)); *data.borrow_mut() += 1; // Mutable access via RefCell

use std::rc::Rc; use std::cell::RefCell; let data = Rc::new(RefCell::new(5)); *data.borrow_mut() += 1; // Mutable access via RefCell

This allows mutation through immutable Rc references but maintains memory safety. The Rust 2024 edition introduces enhancements to pattern matching that improve handling of immutable references, making code more ergonomic without explicit ref or mut keywords in many cases. Under the new match ergonomics (RFC 3627), patterns can infer reference types from context, allowing & patterns to match against &mut targets seamlessly and reducing verbosity in immutable bindings. This update streamlines immutable data deconstruction in match expressions while preserving the borrow checker's guarantees. Rust's approach to immutability via and borrowing draws comparisons to memory-safe languages like Ada and , which also prioritize compile-time safety but differ in mechanisms. Ada's strong typing and lack of unrestricted pointers prevent many memory errors, similar to Rust's borrow checker, though Ada relies more on runtime checks for dynamic allocation. combines garbage collection with safe defaults, offering immutability through const and immutable qualifiers, but lacks Rust's fine-grained ownership for zero-cost abstractions. These languages share Rust's goal of eliminating common vulnerabilities like buffer overflows, with Rust emphasizing performance-critical systems.

Functional Languages (e.g., Scala, )

In functional languages like and Scala, immutability serves as a foundational principle that enables pure functions, , and efficient handling of data structures without side effects. , as specified in its 1998 language report, enforces immutability by default for all data bindings, meaning once a value is assigned to a name, it cannot be altered, which aligns with the language's emphasis on purity. Pure functions in produce outputs solely determined by their inputs, with no observable side effects such as mutation or I/O unless explicitly managed through monads. This design promotes reasoning about code as mathematical expressions, where the same inputs always yield identical results regardless of execution context. Haskell's strategy further leverages immutability by delaying computation until values are needed, allowing shared substructures across expressions through thunks to avoid redundant allocations and computations. , a hallmark of , relies on immutability to ensure that any expression can be replaced by its evaluated value without altering the program's meaning or behavior. For instance, folding over an immutable list, such as computing the sum with foldr (+) 0 [1,2,3], processes elements functionally without modifying the original structure, preserving transparency and enabling optimizations like fusion. Recent advancements in the (GHC) version 9.8, released in , include the Compact module, which optimizes garbage collection for long-lived immutable data structures by compacting them into contiguous regions, reducing fragmentation and improving performance for persistent structures. Scala builds on functional principles while running on the JVM, making immutable data structures central to its collections library, where types like Seq and Map default to immutable implementations that resist in-place modifications. The language favors val declarations for immutable bindings, which cannot be reassigned after initialization, over var for mutable ones, encouraging safer and more predictable code. This preference supports Scala's hybrid nature, as it interoperates seamlessly with Java's mutable objects—such as using Java collections within Scala code—but advises wrapping or converting them to immutable forms to maintain functional idioms. Scala 3, released in March 2021, enhances immutability support through native enums, which define sealed, immutable algebraic data types as case objects or classes, facilitating pattern matching and exhaustive checks without runtime mutability risks.

Other Languages (e.g., , Go)

Go lacks language-level immutability primitives, but effective immutability can be enforced for structs by using unexported (lowercase) fields, which prevent external packages from modifying them directly, combined with getter methods for read access. This approach aligns with Go's philosophy of explicit concurrency safety, where immutable values are safely passed over channels without modification risks during transmission, a feature available since Go's initial release in 2009. Other languages exhibit varying degrees of support for immutability. PHP introduced readonly properties in version 8.1 (released November 2021), allowing properties to be set only during object construction and preventing subsequent changes, with readonly classes in PHP 8.2 (2022) extending this to all properties by default for fully immutable objects. In Perl, the constant pragma declares compile-time constants as subroutines that return fixed scalar or list values, enforcing immutability by replacing the symbol with its value at compile time, a mechanism dating back to Perl 5. Racket supports immutable structs natively through its struct definition, where fields are unchangeable after creation unless explicitly declared mutable, promoting functional programming patterns with optimizations for immutable data. Swift emphasizes immutability via the let keyword for constants, which cannot be reassigned, and value types like structs that are copied on assignment rather than referenced, reducing shared mutable state; these features were refined in Swift 5 (March 2019) with improved copy-on-write semantics. Immutability has seen growing adoption in web development languages during the 2010s, particularly for reactive frameworks like React (initially released in 2013), where immutable state updates enable efficient change detection and predictable UI rendering without direct mutations.

References

  1. https://www.[researchgate](/page/ResearchGate).net/publication/350577964_Immutability_and_Design_Patterns_in_Ruby
Add your contribution
Related Hubs
User Avatar
No comments yet.