Skip to content

Combining Capabilities

When we talked about fields in the classes and variables chapters, we passed over the detail of field capabilities. Fields, just like variables, have their own capabilities! A val field still refers to something permanently immutable. A tag field still can’t be read from. An iso field is still globally unique: it can only be accessed except through this field of a single instance.

Once we have fields with capabilities inside objects with capabilities, now we have two capabilities to keep track of. When a field of an object is accessed or extracted, its reference capability depends both on the reference capability of the field and the reference capability of the origin, that is, the object the field is being read from. We have to pick a capability for the combination that maintains the guarantees for both the origin reference capability, and for the capability of the field.

Viewpoint adaptation

The process of combining origin and field capabilities is called viewpoint adaptation. That is, the origin has a viewpoint, and its fields can be “seen” only from that viewpoint.

Let’s start with a table. This shows how a field of each capability looks when using an origin of each capability.


iso field trn field ref field val field box field tag field
iso origin iso tag tag val tag tag
trn origin iso box box val box tag
ref origin iso trn ref val box tag
val origin val val val val val tag
box origin tag box box val box tag
tag origin n/a n/a n/a n/a n/a n/a

For example, if you have a trn origin and you read a ref field, you get a box result:

class Foo
  var x: String ref

class Bar
  fun f() =>
    var y: Foo trn = get_foo_trn()
    var z: String box = y.x

Explaining why

That table will seem totally natural to you, eventually. But probably not yet. To help it seem natural, let’s walk through each cell in the table and explain why it is the way it is.

Reading from an iso variable

Anything read through an iso origin has to maintain the isolation guarantee that the origin has. The key thing to remember is that the iso can be sent to another actor and it can also become any other reference capability. So when we read a field, we need to get a result that won’t ever break the isolation guarantees that the origin makes, that is, read and write uniqueness.

An iso field makes the same guarantees as an iso origin, so that’s fine to read. A val field is globally immutable, which means it’s always ok to read it, no matter what the origin is (well, other than tag).

Everything else, though, can break our isolation guarantees. That’s why other reference capabilities are seen as tag: it’s the only type that is neither readable nor writable.

Reading from a trn variable

This is like iso, but with a weaker guarantee (write uniqueness as opposed to read and write uniqueness). That makes a big difference since now we can return something readable when we enforce our guarantees.

An iso field makes stronger guarantees than trn, and can’t alias anything readable inside the trn origin, so it’s perfectly safe to read.

On the other hand, trn and ref fields have to be returned as box. It might seem a bit odd that trn has to be returned as box, since after all it guarantees write uniqueness itself and we might expect it to behave like iso. The issue is that trn, unlike iso, can alias with some box variables in the origin. And that trn origin still has to make the guarantee that nothing else can write to fields that it can read. On the other hand, trn still can’t be returned as val, because then we might leave the original field in place and create a val alias, while that field can still be used to write! So we have to view it as box.

Immutable and opaque capabilities, though, can never violate write uniqueness, so val, box, and tag are viewed as themselves.

Reading from a ref variable

A ref origin doesn’t modify its fields at all. This is because a ref origin doesn’t make any guarantees that are incompatible with its fields.

Reading from a val variable

A val origin is deeply and globally immutable, so all of its fields are also val. The only exception is a tag field. Since we can’t read from it, we also can’t guarantee that nobody can write to it, so it stays tag.

Reading from a box variable

A box variable is locally immutable. This means it’s possible that it may be mutated through some other variable (a trn or a ref), but it’s also possible that our box variable is an alias of some val variable.

When we read a field, we need to return a reference capability that is compatible with the field but is also locally immutable.

An iso field is returned as a tag because no locally immutable reference capability can maintain its isolation guarantees. A val field is returned as a val because global immutability is a stronger guarantee than local immutability. A box field makes the same local immutability guarantee as its origin, so that’s also fine.

For trn and ref we need to return a locally immutable reference capability that doesn’t violate any guarantees the field makes. In both cases, we can return box.

Reading from a tag variable

This one is easy: tag variables are opaque! They can’t be read from.

Writing to the field of an object

Like reading the field of an object, writing to a field depends on the reference capability of the object reference being stored and the reference capability of the origin object containing the field. The reference capability of the object being stored must not violate the guarantees made by the origin object’s reference capability. For example, a val object reference can be stored in an iso origin. This is because the val reference capability guarantees that no alias to that object exists which could violate the guarantees that the iso capability makes.

Here’s a simplified version of the table above that shows which reference capabilities can be stored in the field of an origin object.


iso object trn object ref object val object box object tag object
iso origin
trn origin
ref origin
val origin
box origin
tag origin

The bottom half of this chart is empty, since only origins with a mutable capability can have their fields modified.