capsule<T>
Capsule<T> is a compiler-reserved special type, like Unique<T>. It is a move-only, isolated owner: it wraps a single value whose isolation the compiler can fully account for, so the value can be moved — and sent across a channel — without smuggling shared or aliased state along with it.
Contract
Capsule(value) moves value into an isolated owner. The capsule itself does not allocate; allocation behavior belongs to the payload type. Capsule<T> is move-only and can cross channel worker boundaries only when T satisfies the compiler's isolation rules.
The payload can be observed or mutated only through scoped callbacks. Callback errors are propagated by .with and .update; the capsule operations do not introduce their own error set.
Constructing
let c = Capsule(42); // Capsule<i32>
let s = Capsule(try ("hi").clone()); // Capsule<^str>Capsule(value) moves value into the capsule. The source is consumed:
let s = try ("hi").clone();
let c = Capsule(s);
_ = s; // error: use of moved value `s`Capsule<T> is move-only (never Copy) and has the same in-memory representation as T (the payload is stored inline).
Isolation rules
T must be isolatable. Allowed:
- scalars (integers, floats,
bool), enums, tuples, fixed arrays - plain owned structs (all fields isolatable, no user-defined
drop) - the compiler-recognized owned containers
^strand^[T]when their element types are isolatable
Rejected:
- checked references
&Tand raw pointers*T - unqualified
Future<T>,Future<T> where Send, scoped Join, channel and other runtime handles - erased callables / closures
- user-defined
droptypes
let p: *i32 = null;
let c = Capsule(p); // error: `*i32` cannot be placed in a capsuleChannel send-safety
A Capsule<T> is send-safe exactly when T passes the isolation check above. This makes it the way to move an owned container across worker threads even when the bare container is not itself send-safe:
let (tx, rx) = channel.bounded<Capsule<^str>>(1); // ok
await tx.send(Capsule(try ("hi").clone())); // ok
let (tx2, rx2) = channel.bounded<^str>(1);
await tx2.send(try ("hi").clone()); // error: cannot move value of type `^str`
// across worker threadsChannel payload cleanup may drop a Capsule<T> on the receiving/runtime owner side; this is sound because only compiler-recognized isolatable/drop behaviour is ever placed in a capsule.
Scoped access (.with / .update)
Use .with for read-only scoped access to the payload:
var c = Capsule(State { count: 10 });
let n = c.with((state: State) -> i32 {
return state.count;
});Use .update for scoped mutable access:
let next = c.update((state: &State) -> i32 {
state.count = state.count + 1;
return state.count;
});The callback result is the result of the method call. If the callback throws, the method call throws the same error set and must be handled with try or catch.
let value = try c.with((state: State) throws(ReadError) -> i32 {
if state.count < 0 {
throw ReadError.Invalid;
}
return state.count;
});The access reference is only valid during the callback. .with requires a T callback parameter; .update requires &T. Callbacks must use an explicit closure return type so the compiler can infer the method result type.
Related Modules
Use std.channel for cross-worker movement of send-safe payloads and std.async for the runtime boundaries that make send safety matter. Use the language Ownership chapter for the broader ownership model.
Status
Capsule<T> is a current public compiler-reserved ownership type documented in the standard-library section because it is commonly used with std.channel. The isolation accept/reject list above is the current public contract.