Ownership and Affine Values
Who is responsible for a value, and what happens when that responsibility changes?
Ownership answers a practical question: which part of the program is responsible for a value right now? Hew uses that answer to release memory and external resources at predictable points, and to prevent two actors from gaining unsafe access to the same mutable thing.
Start with ordinary values
Numbers, strings, and cloneable records and collections are ordinary data. Reusing ordinary data preserves independent value semantics: changing one logical value does not silently change another. Hew may share immutable or copy-on-write storage internally, but that optimization is not visible in program behaviour.
fn count(values: Vec<i64>) -> i64 {
values.len()
}
fn main() {
var values: Vec<i64> = [10, 20];
println(count(values)); // 2
values.push(30);
println(values.len()); // 3: values remained usable
var copy = clone values;
copy.push(40);
println(values.len()); // 3
println(copy.len()); // 4
}The parameter to count is borrowed for the call, so the caller can keep using values. The explicit clone creates another owned logical value because the example wants both versions at once. clone value and value.clone() mean the same thing when the type supports cloning.
Borrowing is usually implicit
Hew does not use a source-level &T reference type for ordinary code. A regular function parameter borrows where that is sufficient. Write consume value: T when a function must accept ownership. Ordinary data can still provide a copy when the caller needs its value afterward; an affine value cannot.
Understand “affine”
An affine value has at most one live owner. It can be used, moved somewhere else, consumed early, or allowed to clean up at scope exit. It cannot be duplicated as if there were two independent owners. This is useful for values that represent one file, one socket end, one task result, or one cleanup obligation.
“Affine” describes the rule, not a prerequisite. In day-to-day code, the compiler points to the move and the attempted later use. The fix is usually to keep using the new owner, pass the value later, or choose a type whose API provides a real clone operation.
#[resource]
type Session {
id: i64;
}
impl Session {
fn close(consume self) {
println(f"closed session {self.id}");
}
}
fn run(consume session: Session) {
println(f"using session {session.id}");
} // session closes here
fn main() {
let session = Session { id: 7 };
run(session); // ownership moves into run
}After run(session), main no longer owns that session. The compiler would reject another use in main. The receiving function owns the value and its automatic cleanup.
Use resources and linear values
Hew has two related single-owner contracts:
| Contract | Use it when | At scope exit |
|---|---|---|
#[resource] | One safe default cleanup action can release the value | The compiler calls close(consume self) if the owner is still live |
#[linear] | The caller must choose an explicit terminal action such as commit or rollback | An unconsumed value is a compile error |
A resource may be closed early; that consumes it and prevents a second close at scope exit. Keep a fallible operation such as finish, flush, or commit separate so its Result remains visible to the caller. Hew does not support a user-defined Drop implementation.
Containers inherit the strongest ownership rule of what they contain. A Vec<Session>, for example, is affine because duplicating the vector would otherwise duplicate its session owners. Removing an element transfers that element to the caller.
Advanced: how Hew chooses between a copy and a move
A last use of ordinary data may be implemented as a move because no later value needs it. If the caller reuses ordinary data, Hew preserves a second logical value, using a copy, retain, or copy-on-write operation appropriate to the type. This implementation choice does not change source semantics.
Affine data has no general copy path. Rebinding it, passing it to a consuming parameter, closing it, or transferring it across an admitted actor boundary ends the source ownership. Tasks are affine too: await task consumes the task handle and extracts its one result.
Rc<T> and Weak<T> provide shared identity inside one actor. They do not cross actor boundaries. Actor handles are different again: copying a handle preserves the identity of the actor it names.
Apply the model
- See how values cross actors in Actor Isolation and Message Passing.
- Use authority confinement to give a component only the file or service access it needs.
- Check exact resource declarations in the type-system guide and language reference.
- Look up the released contracts in the ownership reference and generated API pages.