1. Reference

The keywordref is a keyword that is placed before the declaration of a variable. It is used to refer to a value, which is usually borrowed from another variable. They are performing similar operation as Pointers, with the difference that they does not need to be dereferenced (this is done automatically), and pointer arithmetics is not possible with references. In Ymir references are always set, and are always set from another variable, hence they are way safer than pointers, and must be prefered to them when possible.

def foo () {
    let x = [1, 2, 3];
    let ref y = ref x;
    //          ^^^    
    // Try to remove the keyword ref.
}


The above program can be represented in memory as shown in the following figure.


drawing


In this figure, one can note that y, is a pointer to x, which can be used as if it was directly x. This means that y must have the same mutability properties (or lower) as x. And that if x is mutable, changing the value of y would also change x.

A first example of reference is presented in the following source code. In this example, a mutable variable x contains a value of type i32. This value is placed on the stack, as it is not a aliasable type. Then a variable y is constructed as a reference of the variable x. Modifying y in the following example, also modifies x.

def main ()
    throws &AssertError
{
    let mut x = 12; // place a value of type i32 and value 12 on the stack
    let ref mut y = ref x; // create a reference of x
    y = 42; // modify the value pointed by the reference
    assert (x == 42);
}


A more complexe example is presented in the following source code. In this example, a deeply mutable array x is created. This array is a reference on borrowed data in the heap. A deeply mutable reference y is the, made on that variable x, which is allowed because x is also deeply mutable and the mutability level of x and y are the same. When changing the value of y (here the reference of the slice), it does not only change the reference of y but also the reference of x.

def main () {
    let mut x : [mut i32] = [1, 2, 3];
    let ref mut y : [mut i32] = ref x;
    y = [7, 8, 9]; // modify the value pointed by the reference (in the stack)
    y [0] = 89; // modify the value on the heap
    assert (x == [89, 8, 9]); 
}

1.1. Reference as function parameter

A parameter of a function can be a reference. As with the local variable, when a value is passed to it, you must tell the compiler that you understand that you are passing the value by reference, and accept the side effects it may have on your values.

import std::io

def foo (ref mut x : i32) {
    x = 123;
}

def main () {
    let mut x = 12;
    //  ^^^
    // Try to remove the mut
    foo (ref x);
    //   ^^^
    // Try to remove the ref
    println (x); 
}


The following figure shows the memory status of the previous code:

drawing

The keyword ref is not always associated with a mutable variable, it can be used to pass a complex type to a function more efficiently, when you don't want to make a complete copy, which would be much less efficient. In this case, you should always specify that you pass the variable by reference, to distinguish it from the function that passes the variable directly by value. In practice, due to the existence of aliasable types, which will be discussed in the next chapter, you will never gain anything by doing this.

import std::io

def foo ( x : i32) {
//       ^
// Try to add mut here
    println ("By value : ", x);
}

def foo (ref x : i32) {
    println ("By reference : ", x);
}

def main () {
    let x = 89;
    foo (x);
    foo (ref x);
}

Results:

By value : 89
By reference : 89


If you have done the exercise, and added the keyword mut to the signature of the first function foo, you should get the following error:

Error : a parameter cannot be mutable, if it is not a reference
 --> main.yr:(3,15)
 3  ┃ def foo (mut  x : i32) {
    ╋               ^


This error means that the type of x is not aliasable, so if it is not a reference, marking it as mutable will have no effect on the program, so the compiler does not allow it.

1.2. Reference as a value

A reference is not a type, it is only a kind of variable, you cannot store references in subtypes (for example, you cannot make an array of references, or a tuple containing a reference to a value). This means that with the following code, you should will get an error.

def main () {
    let x = 12;
    let y = (10, ref x);
}


The following error means that the source code intended to create a reference on a variable, but the compiler will not make it, as it has no interest and will be immediately dereferenced to be stored in the tuple value.

Warning : the creation of ref has no effect on the left operand
 --> main.yr:(3,22)
 3  ┃     let y = (10, ref x);
    ╋                      ^


ymir1: fatal error: 
compilation terminated.

1.3. Reference as function return

You may be skeptical about the interest of returning a reference to a variable, and we agree with you. That is why, it is impossible to return a reference to a variable as a function return value.

import std::io

def foo () -> ref i32 {
    let x = 12;
    ref x
}

def main () {
    let ref y = ref foo (); // x would no longer exists
    println (y); // and a seg fault would be raised, when using the reference
}


With the above source code, the compiler return this fairly straightforward error.

Error : cannot return a reference type
 --> main.yr:(3,19)
 3  ┃ def foo () -> ref i32 {
    ╋                   ^^^

results matching ""

    No results matching ""