I think there are 2 kinds of immutability:
1. Strong immutability
* If a object is referred by a immutable reference, it never change.
(According to Rust's the book, "In Rust, the compiler guarantees that when you
state that a value won’t change, it really won’t change.")
2. Weak immutability (per symbol immutability)
* Modifying a object using a immutable reference is error.
* A object referred by immutable reference can be modified by using mutable
reference.
(Const reference in C++ is this. You can change the value of what const
reference refer without using pointer)
Immutability in Nim seems compiler forbids changing value using an immutable
symbol but it doesn't guarantee the value never change.
Following code can be compiled without error.
proc foo(imu: string; mut: var string) =
echo imu
mut[0] = 'a'
echo imu
var text = "hello"
foo(text, text)
Run
Output:
hello
aello
Run
When `ref` is used, multiple `ref` symbols can refer 1 object. `ref` can refer
a object indirectly like:
var
n:Node = initNode()
x:Node = n
y:Node = initNode()
y.le = n
Run
Perfect strong immutability would be like if a object is referred by at least 1
immutable reference directly or indirectly, modifying it is compiler error. A
object can be modified when it is referred only by mutable symbols.
proc addData(x: mut Node; y: Node) =
x.data.add(y.data)
var n:Node = initNode()
addData(n, n) #Compile error because x[] is referred by y and y is
immutable.
var m:Node = initNode()
m.le = n
addData(n, m) #Compile error because x[] is referred by y.le and y is
immutable.
m.le = nil
addData(n, m) #No compile error. x[] is not referred by any immutable
symbols.
Run
But which symbol refer which object is determined at runtime and perfect strong
immutability cannot be achieved at compile time. (It might be possible at
runtime if each object have a counter that counts number of immutable
references that refering the object. But I don't think it is what Nim user
wants.) For example:
proc select(a, b: Node): Node =
result = if oracle(): a else: b
proc addData(x: mut Node; y: Node) =
x.data.add(y.data) #Compile error if y == x but not if y != x?
var
n:Node = initNode()
m:Node = initNode()
addData(n, select(n,m))
Run
So making strong immutability at compile-time could be like conservatively
forbid any changing of objects if they can be referred by any immutable
reference. Then, `proc addData(x: mut Node; y: Node)` is always compile error.
Any procedures that have at least 2 same type parameters and one of them is
immutable and other is `mut` result in compile error. People have to use `mut`
parameters even if they are supposed to be not modified.
proc replace(n, sub, by:mut Node):Node =
## If ``sub`` is subtree of ``n``, replace it with ``by``.
## ``sub`` and ``by`` are supposed to be not modified but compiler don't
think so.
Run
Conservative strong immutability seems like hard to write code or every
parameters become `mut`.
Strong immutability might be possible without any runtime check/cost by copying
rust's ownership, borrowing and references.
[https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html)
> At any given time, you can have either one mutable reference or any number of
> immutable references.
>
> References must always be valid.
You cannot have a mutable reference while you have an immutable one. And you
cannot have multiple mutable references. So you will get compile-error from
this code:
proc addData(x: mut Node; y: Node)
var
n:Node = initNode()
addData(n, n)
Run
Per symbol immutability only forbid modification using immutable symbols.
Objects referred by immutable symbols could be changed using mutable symbols.
Per symbol deep immutability would be like compiler forbid changing any fields
of immutable symbols. It still partially protect people from setting value to
wrong variable and procedure signature can tell which parameters supposed to
modify a object or not.
proc foo(imu: Node; mu: Node) =
imu.data = "foo" #Error
imu.le.data = "foo" #Error
mu.data = "foo" #OK, even if imu == mu
mu.le.data = "foo" #OK, even if imu.le == mu
#Both harmless and harmful are vaild.
proc harmless(a, b: Node) =
var x = construct(a, b)
x.data = "mutated"
proc harmful(a, b: Node) =
var x = select(a, b)
x.data = "mutated"
Run
If you want strong immutability, copying rust's rule could be a goot option
unless there is better idea. But some people might think it is complicated or
don't like it. I think per symbol immutability is simple and it is still better
than there is no way to make `ref` immutable.