OxCaml logo Jane Street logo

Uniqueness Reference

The goal of this document is to be a reasonably complete reference to the uniqueness mode. For a gentler introduction, see the introduction. It will grow over time as we add more features such as overwriting and borrowing.

More details on uniqueness checking

Mixing unique and aliased uses

Continuing the example from the introduction, we can have both a unique and an aliased access to a value, as long as those occur in separate branches:

val free : t @ unique -> unit
val store : t @ aliased -> unit

let okay t =
  if flip_coin ()
  then free t
  else (store t; store t)

This might be surprising coming from a language that uses ownership like Rust. In Rust, each value needs to have exactly one owner which is responsible for its deallocation. But in OCaml, we can rely on the GC for deallocation which makes it possible to give up ownership of the value in the second branch.

In particular, this enables a programming pattern to create data structures in two phases: In the first phase, the data structure is unique and we can mutate it safely and efficiently. In the second phase, we relinquish the uniqueness and the data structure becomes an ordinary OCaml value which can be shared freely.

Unique uses of different fields

The uniqueness analysis makes it possible to consume some fields of an allocation without losing access to the allocation or its other fields. For example, you might write:

let okay r =
  free r.field1;
  match r with
  | { field2; _ } -> free field2

This is fine: we free the first field of r, then pattern match on r itself (which was not affected by the free of its child) and then free the second child. While r technically still keeps a reference to r.field1, it is never dereferenced after the free.

However, it would not be fine to free the first field and then free r itself:

let bad r =
  free r.field1;
  match r with
  | { field2; _ } -> free r

The problem here is that the compiler can not determine whether free r might dereference r.field1 again. As such, it would be unsafe to free r.field1. The uniqueness analysis carefully tracks which children of an allocation have already been consumed. This tracking also works with aliases, such as renamings:

let okay r =
  let x = r in
  free x.field1;
  match r with
  | { field2; _ } -> free field2

However, which children have been consumed is not tracked through more complicated aliases, such as function calls:

let bad r =
  let x = Fun.id r in
  free x.field1;
  match r with
  | { field2; _ } -> free field2

or new allocations:

let bad r =
  let x = { field1 = r.field1; field2 = r.field2 } in
  free x.field1;
  match r with
  | { field2; _ } -> free field2

Matching on tuples

In OCaml, it is possible to simultaneously match on multiple values:

match x, y, z with
| p, q, r -> ...

There is in fact no special syntax for this: as parentheses are optional in tuples, the above is actually a match on a single value, the tuple (x, y, z), against a single pattern, the pattern (p, q, r). Normally, this would mean that x, y and z have the same mode. However, just as for other modes, we check this code as if it was special syntax and the modes of x, y, z may be unrelated.

Furthermore, we track aliasing through tuples that are immediately matched on:

let okay x y z =
  match x, y, z with
  | p, q, r -> free x.field1; free p.field2

Here we free both the first and the second field of x. However, this changes when the tuple is explicitly named:

let bad x y z =
  match x, y, z with
  | p, q, r as t -> free x.field1; free p.field2

In this case, the OCaml compiler may actually allocate a tuple (x, y, z) at runtime and we now consider the value x to be consumed by the tuple. That makes it impossible to free its first field, which can now be referenced through the new allocation t.