Flix supports references in the ML-tradition. The three key operations are ref e, deref e, and e := e. The ref e operation allocates a reference cell in the heap and returns its location, the deref operation dereferences a location and returns the content of a reference cell, and finally the assigment := operation changes the value of a reference cell. Informally, a reference cell can be thought of as an "object" with a single field that can be changed.

All operations on references are impure. As such, all functions that use references must be marked with annotation \ IO for Impure or be casted to Pure.


A reference cell is allocated as follows:

ref 42

which evaluates to a value of type Ref[Int32] which is a reference (pointer) to a single memory cell that holds the value 42.


A reference cell is accessed (de-referenced) as follows:

let l = ref 42;
deref l

which evaluates to 42 as expected.


A reference cell can have its value updated as follows:

let l = ref 42;
l := 84;
deref l

which evaluates to 84 as expected.

Example: A Simple Counter

The following program models a simple counter that can be incremented:

enum Counter {
    case Counter(Ref[Int32])

def newCounter(): Counter \ IO = Counter(ref 0)

def getCount(c: Counter): Int32 \ IO =
    let Counter(l) = c;
    deref l

def increment(c: Counter): Unit \ IO =
    let Counter(l) = c;
    l := (deref l) + 1

def f(): Unit \ IO =
    let c = newCounter();
    getCount(c) |> println

Note that the newCounter, getCount, increment and f functions must all be marked as Impure with the annotation \ IO.

Aliasing and References to References

References naturally support aliasing since that is exactly their purpose. For example:

let l1 = ref 42;
let l2 = l1;
l2 := 84;
deref l1

Evaluates to 84 because the reference cell that l1 points to is modified through the alias l2.

References can point-to references as the following example illustrates:

let l1 = ref 42;
let l2 = ref l1;
deref (deref l2)

Evaluates to 42 as expected.

Design Note

Flix does not support any notion of global mutable state. If you need to maintain a program-wide counter (or other mutable state) then you have to allocate it in the main function and explicitly thread it through the program.

Mutable Tuples and Records

Flix tuples and records are immutable. However, tuples and records may contain mutable references.

For example, here is a pair that contains two mutable references:

let p = (ref 1, ref 2);
fst(p) := 123

The type of the pair is (Ref[Int32], Ref[Int32]). The assignment does not change the pair itself (it is immutable), but rather changes the value of the reference cell in the first component of the pair.

Similarly, here is a record that contains two mutable references:

let r = { fstName = ref "Lucky", lstName = ref "Luke" };
r.fstName := "Unlucky"

The type of the record is { fstName = Ref[String], lstName = Ref[String] }. Again, the assignment does not change the record itself, but rather changes the value of the reference cell corresponding to the fstName field.