1. Pure values
A pure value is a value that cannot change. In other word it is a value, that ensure that there is no variable in the program that has a mutable access to it. Pure values are different to const values, in the sense that const values just give the guarantee that the current access is not mutable, however they does not guarantee that there is no other variable in the program with mutable access.
The different guarantees on values can be listed as follows :
- No guarantee, the value is mutable
mut
- Guarantee that the value is no writable in the current context
const
- Guarantee that the value is completely immutable in every context
pure
1.1. Limitation of const values
To understand the limitation of const
values, the following
program has two variable defined in the function main
. The first
one a
has a mutable access to the value, and the variable
b
has a const access to the same value. Because the variable
a
modifies the value, the value pointed by b
is also
modified even if it was const
. Thus, it is important to
understand that const
is only referering to the permission of
the variable b
.
import std::io;
def main () {
let dmut a = [1, 2, 3];
let b = a;
a [0] = 98;
println (b);
}
Results:
[98, 2, 3]
We have seen in a previous chapter that the keywords copy
and
dcopy
can be used to remove this limitation, and ensure that the
value of b
is the same as the value of a
, but that
modifying the value of a
does not modify the value of
b
. This mechanism is the base of the one provided by pure
values.
import std::io;
def main ()
throws &OutOfArray
{
let dmut a = [1, 2, 3];
let b = dcopy a;
a [0] = 98;
println (b);
}
Results:
[1, 2, 3]
Now let's add a third variable to the equation, and let's name it
c
. In this variable we want to store the value of b
, and
ensure that the variable is never modified. If the initialization of
the variable b
is made in an obscure way (for example in a
function that is not readable, here the foo
function), then the
only way to ensure that the value of c
is never modified, is to
make a copy of it inside the value of c
. If the memory movement
are easy in the following example, it may not be the case in complex
program with many variable and memory movement.
import std::io;
def foo (a : [i32])-> [i32] {
a
}
def main ()
throws &OutOfArray
{
let dmut a = [1, 2, 3];
let b = foo (a);
let c = dcopy b;
let d = b;
a [0] = 98;
println (c);
println (d);
}
Results:
[1, 2, 3]
[98, 2, 3]
1.2. Purity and the pure keyword
In the above example, both variable b
and d
have no
guarantees, and there values are indeed modified. The pure
keyword can be added to their definitions. In that case, the compiler
checks the initialization of the values that are used, and ensure that
they came from a deep copy, or another pure value.
def foo (a : [i32]) -> [i32] {
a
}
def main ()
throws &OutOfArray
{
let dmut a = [1, 2, 3];
let pure b = foo (a);
}
Error : discard the constant qualifier is prohibited
--> main.yr:(10,11)
10 ┃ let pure b = foo (a);
╋ ^
┃ Note : implicit pure of type [i32] is not allowed, it will implicitly discard constant qualifier
┃ --> main.yr:(10,19)
┃ 10 ┃ let pure b = foo (a);
┃ ╋ ^
┗━━━━━┻━
ymir1: fatal error:
compilation terminated.
To avoid the above error, there is two possibilities. Either we
add a dcopy
on the function call of foo
, or we add
pure
to the return type of foo
and make a deep copy of the
value of a
in this function. The function bar
of the
following example perform the second possibility.
import std::io;
def foo (a : [i32])-> [i32] {
a
}
def bar (a : [i32])-> pure [i32] {
return dcopy a;
}
def main ()
throws &OutOfArray
{
let dmut a = [1, 2, 3];
let pure b = dcopy foo (a);
let pure c = bar (a);
let pure d = b;
let pure e = c;
a [0] = 98;
println (d);
println (e);
}
Results:
[1, 2, 3]
[1, 2, 3]
One can note that not copy are needed to move the value contained
in the variable c
into the variable e
. This is due to the
fact that c
is pure, so the guarantee of purity is already
made. Thanks to the pure
mechanism some copies can be avoided.