We’ve covered the basics of Pony’s type system and then expressions, this chapter about reference capabilities will cover another feature of Pony’s type system. There aren’t currently any mainstream programming languages that feature reference capabilities. What is a reference capability?
Well, a reference capability is built on the idea of “a capability”.
A capability is the ability to do “something”. Usually that “something” involves an external resource that you might want access to; like the filesystem or the network. This usage of capability is called an object capability and is discussed in the next chapter.
Pony also features a different kind of capability, called a “reference capability”. Where object capabilities are about being granted the ability to do things with objects, reference capabilities are about denying you the ability to do things with memory references. For example, “you can have access to this memory BUT ONLY for reading it. You can not write to it”. That’s a reference capability and it’s denying you access to do things.
Reference capabilities are core to what makes Pony special. You might remember in the introduction to this tutorial what we said about Pony:
- It’s type safe. Really type safe. There’s a mathematical proof and everything.
- It’s memory safe. Ok, this comes with type safe, but it’s still interesting. There are no dangling pointers, no buffer overruns, heck, the language doesn’t even have the concept of null!
- It’s exception safe. There are no runtime exceptions. All exceptions have defined semantics, and they are always handled.
- It’s data-race-free. Pony doesn’t have locks or atomic operations or anything like that. Instead, the type system ensures at compile time that your concurrent program can never have data races. So you can write highly concurrent code and never get it wrong.
- It’s deadlock free. This one is easy because Pony has no locks at all! So they definitely don’t deadlock, because they don’t exist.
Reference capabilities are what make all that awesome possible.
Code examples in this chapter might be kind of sparse, because we’re largely dealing with higher-level concepts. Try to read through the chapter at least once before starting to put the ideas into practice. By the time you finish this chapter, you should start to have a handle on what reference capabilities are and how you can use them. Don’t worry if you struggle with them at first. For most people, it’s a new way of thinking about your code and takes a while to grasp. If you get stuck trying to get your capabilities right, definitely reach out for help. Once you’ve used them for a couple weeks, problems with capabilities start to melt away, but before that can be a real struggle. Don’t worry, we all went through that struggle. In fact, there’s a section of the Pony website dedicated to resources that can help in learning reference capabilities. And by all means, reach out to the Pony community for help. We are here to help you get over the reference capabilities learning curve. It’s not easy. We know that. It’s a new way of thinking for folks, so do please reach out. We’re waiting to hear from you.
Scared? Don’t be. Ready? Good. Let’s get started.
So if the object is the capability, what controls what we can do with the object? How do we express our access rights on that object?
In Pony, we do it with reference capabilities.
Rights are part of a capability If you open a file in UNIX and get a file descriptor back, that file descriptor is a token that designates an object - but it isn’t a capability. To be a capability, we need to open that file with some permission - some access right.
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.
An important part of Pony’s capabilities is being able to say “I’m done with this thing.” We’ll cover two means of handling this situation: consuming a variable and destructive reads.
Consuming a variable Sometimes, you want to move an object from one variable to another. In other words, you don’t want to make a new name for the object, exactly, you want to move the object from some existing name to a different one.
A recover expression lets you “lift” the reference capability of the result. A mutable reference capability (iso, trn, or ref) can become any reference capability, and an immutable reference capability (val or box) can become any immutable or opaque reference capability.
Why is this useful? This most straightforward use of recover is to get an iso that you can pass to another actor. But it can be used for many other things as well, such as:
Aliasing means having more than one reference to the same object, within the same actor. This can be the case for a variable or a field.
In most programming languages, aliasing is pretty simple. You just assign some variable to another variable, and there you go, you have an alias. The variable you assign to has the same type (or some supertype) as what’s being assigned to it, and everything is fine.
Reference capabilities make it safe to both pass mutable data between actors and to share immutable data amongst actors. Not only that, they make it safe to do it with no copying, no locks, in fact, no runtime overhead at all.
Passing For an object to be mutable, we need to be sure that no other actor can read from or write to that object. The three mutable reference capabilities (iso, trn, and ref) all make that guarantee.
Subtyping is about substitutability. That is, if we need to supply a certain type, what other types can we substitute instead? Reference capabilities factor into this.
Simple substitution First, let’s cover substitution without worrying about ephemeral types (^) or alias types (!). The <: symbol means “is a subtype of” or alternatively “can be substituted for”.
iso <: trn. An iso is read and write unique, and a trn is just write unique, so it’s safe to substitute an iso for a trn.
When a field of an object is read, 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.
This is because all the guarantees that the origin reference capability makes have to be maintained for its fields as well.
Viewpoint adaptation The process of combining origin and field capabilities is called viewpoint adaptation.
When we talked about reference capability composition and viewpoint adaptation, we dealt with cases where we know the reference capability of the origin. However, sometimes we don’t know the precise reference capability of the origin.
When that happens, we can write a viewpoint adapted type, which we call an arrow type because we write it with an ->.
Using this-> as a viewpoint A function with a box receiver can be called with a ref receiver or a val receiver as well since those are both subtypes of box.
At this point, it’s quite possible that you read the previous sections in this chapter and are still pretty confused about the relation between reference capabilities. It’s okay! We have all struggled when learning this part of Pony, too. Once you start working on Pony code, you’ll get a better intuition with them.
In the meantime, if you still feel like all these tidbits in the chapter are still scrambled in your head, there is one resource often presented with Pony that can give you a more visual representation: the reference capability matrix.