recover expression let's you "lift" the reference capability of the result. A mutable reference capability (
ref) can become any reference capability, and an immutable reference capability (
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:
- Creating a cyclic immutable data structure. That is, you can create a complex mutable data structure inside a
recoverexpression, "lift" the resulting
- "Borrow" an
ref, do a series of complex mutable operations on it, and return it as an
- "Extract" a mutable field from an
isoand return it as an
What does this look like?
recover Array[String] end
That's a simple one: it returns an
Array[String] iso, instead of the usual
Array[String] ref you would get.
Here's a more complicated example from the standard library:
recover var s = String((prec + 1).max(width.max(31))) var value = x try if value == 0 then s.push(table(0)?) else while value != 0 do let index = ((value = value / base) - (value * base)) s.push(table(index.usize())?) end end end _extend_digits(s, prec') s.append(typestring) s.append(prestring) _pad(s, width, align, fill) s end
format/_FormatInt. It creates a
String ref, does a bunch of stuff with it, and finally returns it as a
Both of those examples use the default reference capability for a
recover expression, since they don't specify one. The default for any mutable reference capability is
iso and the default for any immutable reference capability is
val. You can also give an explicit one:
let key = recover line.substring(0, i).strip() end
net/http/_PayloadBuilder. We get a substring of
line, which is a
String iso^, then we call strip on it, which returns itself. But since strip is a
ref function, it returns itself as a
String ref^ - so we use a
recover val to end up with a
How does this work?
recover expression, your code only has access to sendable values from the enclosing lexical scope. In other words, you can only use
tag things from outside the
This means that when the
recover expression finishes, any aliases to the result of the expression other than
tag ones won't exist anymore. That makes it safe to "lift" the reference capability of the result of the expression.
recover expression could access non-sendable values from the enclosing lexical scope, "lifting" the reference capability of the result wouldn't be safe. Some of those values could "leak" into an
val result, and result in data races.
Automatic receiver recovery
When you have an
trn receiver, you normally can't call
ref methods on it. That's because the receiver is also an argument to a method, which means both the method body and the caller have access to the receiver at the same time. And that means we have to alias the receiver when we call a method on it. The alias of an
iso is a
tag (which isn't a subtype of
ref) and the alias of a
trn is a
box (also not a subtype of
But we can get around this! If all the arguments to the method at the call-site are sendable, and the return type of the method is either sendable or isn't used at the call-site, then we can "automatically recover" the receiver. That just means we don't have to alias the receiver - and that means we can call
ref methods on an
trn are both subtypes of
Notice that this technique looks mostly at the call-site, rather than at the definition of the method being called. That makes it more flexible. For example, if the method being called wants a
ref argument, and we pass it an
iso argument, that's sendable at the call-site, so we can still do automatic receiver recovery.
This may sound a little complicated, but in practice, it means you can write code that treats an
iso mostly like a
ref, and the compiler will complain when it's wrong. For example:
let s = recover String end s.append("hi")
Here, we create a
String iso and then append some text to it. The append method takes a
ref receiver and a
box parameter. We can automatically recover the
iso receiver since we pass a
val parameter, so everything is fine.