The primary role of permissions is to improve program safety by preventing race conditions, where multiple concurrent threads step on each other when trying to access the same value. Prudent use of permissions can also help make code easier to understand and improve execution performance.

Every variable, field and reference declares its permission, which enables and constrains its use. For example, different permissions grant or deny the right to read a value, modify a value, share references to the same value, or share access to values across threads.

Permissions come in two flavors:

imm and mut

These two static permissions were introduced in an earlier chapter. Most variable and struct field declarations use one of these two permissions:

uni

The static uni (unique) permission allows its protected value to be read or changed (similar to mut). However, it carries a significant restriction: there can only be one active reference to its value at a time. Ownership of such a reference may be moved around, including (importantly) to another thread.

The uni permission, and its single reference restriction, may seem a bit unusual. However, the concept is not new to Cone. Rust's &mut reference, Pony's iso reference capability, and C's restrict keyword accomplish something similar. Let's explore the implications of using this permission.

uni as the first permission

uni is best understood as the first permission an allocator reference gets in the early days of its existence. Whenever a type constructor creates and returns a new allocator reference, it typically has the uni permission. This makes sense: since the just-created reference is the only one in existence, it naturally complies with the single reference restriction.

So long as the reference keeps its uni permission, this single reference to an object can be freely and safely moved around a program, hopping from function-to-function or even thread-to-thread. When a uni reference is assigned to another variable or passed to a function (or returned), the reference moves to its new owner. Any subsequent attempt to use the previous owner of the reference will trigger an error:

imm ref = &gc Point{x: 1.0, y: 3.0} // Create new object and reference
imm newref = ref   // Move reference to newref
imm x = ref.x      // ERROR! ref is no longer usable

uni as the universal donor

In many cases, the single reference restriction of uni poses no hardship and the reference keeps its uni permission throughout the object's lifetime. In addition to flexibility of movement, there can be other benefits to keeping a mutable reference as uni, such as improved performance optimizations and sum type safety.

However, the single reference restriction makes uni references unsuitable for many data structures that require the use of multiple references to the same object. Should a program's logic need multiple references to the same object, the uni reference may be transitioned to a new reference with a sharable permission, such as mut or imm:

imm ref = &gc Point{x: 1.0, y: 3.0} // Create new object and reference
imm newref1 &mut = ref      // Move reference to newref with 'mut' permission
imm newref2 &mut = newref1  // Two 'mut' references to the same object
imm x = newref1.x      // Either reference may be used

This transition may be more concisely performed at reference creation:

imm newref1 = &gc mut Point{x: 1.0, y: 3.0} // Create new 'mut' reference

uni and temporary transitions

In many cases, the transition from uni to a shared reference is a one way trip. You cannot safely transition a mut reference back to a uni reference (or even to an imm reference). The reference is now "frozen" to the restrictions of its new permission: As mut, it can never be shared across threads. As imm, it can never be altered.

Using borrowed references, however, it becomes possible to temporarily transition a reference to another permission. During the lifetime of a borrowed reference, the original uni reference may not be used. However, once the lexical lifetime of the borrowed reference expires, the original uni reference becomes usable again:

imm ref = &gc Point {x: 1, y: 2}
  imm ref2 = &mut ref
  imm ref3 = ref2
  ref2.x = ref3.y
  ref.y = 5    // Error, ref is unusable while borrowed 'ref2' exists
ref.x = 5      // Allowed, since borrowed references have expired

const

The const (constant) permission is valuable for temporary references, particularly borrowed references. Although a const reference may read its value, modification is prohibited. Although this restriction sounds like imm, there are key differences. imm guarantees that no other mutable reference to the same object exists, making it safe to share between threads. const makes no such guarantee. Therefore, const references cannot be safely shared with another thread.

The primary value of const lies with references passed to functions and methods which need to inspect the reference's value but promise to never try to change it. By declaring a reference parameter as const, the function or method can safely accept references that are declared as mut, imm, uni, const, or mutx.

Because const is such a common and universal receiver for other references, a new reference is assumed to have const permission when no permission has been explicitly specified.

mutx

For borrowed references, the mutx permission is the mutable counterpart of const. A mutx reference may inspect or change its value. During its lifetime, it promises that references borrowed from it are either a) multiple const references or b) a single usable mutx reference. mutx may not be shared or moved across threads.

mutx is typically the best permission to specify when a function or method needs a reference able to change its value. This is because mutx can safely accept references that are declared as mut, uni, or mutx. By contrast, a borrowed reference declared as mut would not be able accept linear allocator references, references to sum types, or mutx references.

mutx for sum type allocator references

mutx may also be used on allocator references. Its main purpose is to allow multiple references to a value that guarantee that only one can modify the value at a time and only via a mutx borrowed reference. This can be useful when a program needs multiple mutable allocator references to sum types which, for safety reasons, may never be mut.

Sum types, variable-sized arrays and other types need restrictions on shared mutability for memory safety, even in single-threaded situations. Otherwise, it might be possible for one reference to alter the structure of the object while another reference holds an interior pointer now invalidated by the structural change. Unsafe things could happen if the interior pointer was then used.

Enforcement of this guarantee requires a run-time mechanism. As with all runtime permissions, this means that mutx must be specified as the permission when allocating the first reference to that value.

id

id (identifier) declares that this reference will never be used to read or modify its value. It may only be compared with another reference. It may be derived (coerced) from any reference. Its primary value is for references to actors, thereby enabling invocation of an actor's behaviors without the ability to see or change its state.

Runtime (Synchronization) Permissions

In addition to the static permissions, Cone also offers runtime permissions. Runtime permissions enable the use of shared, mutable references that are not limited by the restrictions placed on mut references. Using runtime permissions, it becomes possible to share value references across threads or to obtain interior references to shared references on sum or array types.

The penalty for this added freedom is that reference use carries a small runtime performance hit. This is because runtime permissions wrap every use of the reference to access its contents with that runtime permission's synchronization mechanism which ensures that only one reference at a time can read or change its value. The other potential drawback is that use of runtime permissions can sometimes suffer from deadlocks or runtime panics.

Runtime Permission Coercions

Runtime permissions do not allow coercion to or from any other permission. Thus, the runtime permission must be specified when creating a new allocator reference to a value. All copies of that reference carry the same permission.

This means that objects protected by runtime permissions cannot use functions that accept const or mutx references. They can only use functions whose reference parameters declare the same runtime permission.

Lock

The Lock permission enables multiple, mutable references to be shared and used across threads. It makes use of hardware intrinsics to ensure only one reference at a time can read or modify the contents of the reference.

imm point &Lock Point = &Gc Lock Point(x:2, y: 3)
thread.sendPoint(point)  // Now another thread has a copy of this reference
point.x += point.y  // Access to point safely protected by Lock twice

Custom Permissions

It is possible to define additional runtime permissions ... TBD

perm NewLock
  ....

Permissions and Threads

Only imm, id, and the runtime "locked" permissions may be shared freely between threads. Furthermore, a uni reference may be moved from one thread to another. References with any other permission (mut, const, and mutx) restrict access to their values to a single thread.

Permissions and Struct fields

TBD

_