Since types are guarantees, it’s useful to talk about what guarantees a reference capability makes.
What is denied
We’re going to talk about reference capability guarantees in terms of what’s denied. By this, we mean: what can other variables not do when you have a variable with a certain reference capability?
We need to distinguish between the actor that contains the variable in question and other actors.
This is important because data reads and writes from other actors may occur concurrently. If two actors can both read the same data and one of them changes it then it will change under the feet of the other actor. This leads to data-races and the need for locks. By ensuring this situation can never occur, Pony eliminates the need for locks.
All code within any one actor always executes sequentially. This means that data accesses from multiple variables within a single actor do not suffer from data-races.
Mutable reference capabilities
The mutable reference capabilities are iso
, trn
and ref
. These reference capabilities are mutable because they can be used to both read from and write to an object.
- If an actor has an
iso
variable, no other variable can be used by any actor to read from or write to that object. This means aniso
variable is the only variable anywhere in the program that can read from or write to that object. It is read and write unique. - If an actor has a
trn
variable, no other variable can be used by any actor to write to that object, and no other variable can be used by other actors to read from or write to that object. This means atrn
variable is the only variable anywhere in the program that can write to that object, but other variables held by the same actor may be able to read from it. It is write unique. - If an actor has a
ref
variable, no other variable can be used by other actors to read from or write to that object. This means that other variables can be used to read from and write to the object, but only from within the same actor.
Why can they be used to write? Because they all stop other actors from reading from or writing to the object. Since we know no other actor will be reading, it’s safe for us to write to the object, without having to worry about data-races. And since we know no other actor will be writing, it’s safe for us to read from the object, too.
Immutable reference capabilities
The immutable reference capabilities are val
and box
. These reference capabilities are immutable because they can be used to read from an object, but not to write to it.
- If an actor has a
val
variable, no other variable can be used by any actor to write to that object. This means that the object can’t ever change. It is globally immutable. - If an actor has a
box
variable, no other variable can be used by other actors to write to that object. This means that other actors may be able to read the object and other variables in the same actor may be able to write to it (although not both). In either case, it is safe for us to read. The object is locally immutable.
Why can they be used to read but not write? Because these reference capabilities only stop other actors from writing to the object. That means there is no guarantee that other actors aren’t reading from the object, which means it’s not safe for us to write to it. It’s safe for more than one actor to read from an object at the same time though, so we’re allowed to do that.
Opaque reference capabilities
There’s only one opaque reference capability, which is tag
. A tag
variable makes no guarantees about other variables at all. As a result, it can’t be used to either read from or write to the object; hence the name opaque.
It’s still useful though: you can do identity comparison with it, you can call behaviours on it, and you can call functions on it that only need a tag
receiver.
Why can’t tag
be used to read or write? Because tag
doesn’t stop other actors from writing to the object. That means if we tried to read, we would have no guarantee that there wasn’t some other actor writing to the object, so we might get a race condition.