Those other languages have a different meaning of mutability, which makes comparisons difficult. There mutation usually means a variable, member, or element’s value can be changed, whereever it is. x += 1
would be mutation of the variable x
to another value; in languages like Rust that default to immutable variables, the compiler complains if you didn’t declare it to be mutable let mut x = 0
.
In Julia, we don’t call reassignable variables, fields, or elements “mutable” in the sense of the other languages. Mutability is about types, and while it involves how you can change something, it’s also about the identity of that value. For immutable types, equal value means the values are one and the same; x = Some(1); y = Some(1)
assigned the same instance to 2 variables. We can still make changes by instantiation and reassignment x = Some(2)
, but then x
and y
no longer share an instance. This allows the same instance to actually be represented by copies of data in optimizations.
For mutable types, instantiation provides the identity; x = [1]; y = x; z = [1]
assigned the same instance to x
and y
, but a different one (even defined by ==
to be of equal value) to z
. We could make the same kind of change with instantiation and reassignment, but we also have the option of mutating the same instance x[1] = 2
that is reflected by both x
and y
. In other words, mutability lets multiple references share an instance and its mutations. This needs an instance to be represented by data in a particular location, usually on the heap but could be on the stack under previously described conditions.
It’s not an entirely different way of life, just a different abstraction of data from the copies, moves, references, and pointers built into other languages. The abstraction in Julia (, Python, and Javascript) being simpler isn’t inherently better, it costs some straightforward control. For example, we might mimic data modification on the stack reliably with an immutable struct (and with the syntax of mutation via Accessors.jl), but full instantiation and reassignment is unnecessary work that we’re relying on the compiler to elide when safe. It’s possible to unsafely skip the constructor to new
for less to elide, though you probably shouldn’t be skipping any enforcement of invariants in either immutable or mutable types.