Ownership
Ownership is the part of C+ that differs most from C. There is no &T and no &mut T. Borrowing is expressed by parameter markers, not by reference types. The call site carries no markers at all; the function signature is the single place that describes the data flow.
The parameter forms
Values borrow by default. A bare x: T is a read-only borrow for every type — the caller keeps ownership. take is the opt-in that says "consume this value", and ref is the opt-in that says "write back into the caller's value".
| Form | On non-Copy types | On Copy types |
|---|---|---|
x: T |
Read-only borrow (caller keeps ownership; callee reads only) | Pass-by-value copy |
ref x: T |
Write-back borrow (callee mutates; the write lands in the caller's value, which must be a var) |
Pass-by-value, locally mutable |
take x: T |
Move (ownership transfers; caller can't use the value after the call) | Pass-by-value |
Method receivers mirror these exactly. The receiver name is always this; ref / take are the modifiers.
| Receiver | Meaning |
|---|---|
this |
Read-only borrow: reads the receiver, caller keeps ownership |
ref this |
Write-back borrow: may mutate, the write lands in the caller's var |
take this |
Move: consumes the receiver, caller can't use it after |
Both functions and methods default to the same common case: a bare x: T (or a bare this) only reads the value and hands ownership back. When you need to consume or to write back, you say so with take or ref, and the marker is always visible in the signature.
Copy is structural
A type is Copy if every component is. Primitives and plain enums are Copy. A struct of Copy fields is Copy automatically. A struct that defines fn drop(ref this) is forced to be non-Copy, because silently bit-copying something that owns a resource would lead to a double free.
struct Point { x: i32, y: i32 } // Copy (all fields Copy)
struct Buf { _ptr: *u8, _len: usize }
impl Buf {
fn drop(ref this) { free(this._ptr); } // forces non-Copy
}
Return values always move:
fn make_buf() -> Buf { ... } // no marker; returning is always a move
When to use take
Since params borrow by default, the question is: when does the callee need to consume the value? Use take whenever the value escapes — returned, stored in a field or global, or re-passed into another take slot. A bare parameter only reads; making it escape would create a second owner, which is E0337 ("use take, or .clone()").
import "stdlib/text" as text;
// `x` escapes (it is returned), so it must be consumed: `take`.
fn echo(take x: text::Text) -> text::Text { return x; }
// Default borrow: caller keeps `s`; callee only reads. To return a string it
// makes its own, typically via .clone().
fn label(x: text::Text) -> text::Text { return x.clone(); }
let s: text::Text = "hello".to_text();
let r: text::Text = label(s); // s still usable after this call
When the compiler sees a bare borrow that escapes its call, it reports E0337 with a precise fix-it: mark the parameter take, or .clone() the value at the escape point.
restrict: opt-in noalias for raw pointers
The borrow checker does not reason about *T raw pointers, so by default LLVM must assume any two pointer arguments may alias. For numeric hot paths that is a real tax: the autovectorizer inserts a runtime alias check and a scalar fallback. The restrict parameter marker asserts that the pointer does not alias any other pointer reachable in the body, and lowers to LLVM noalias.
fn axpy(n: usize, a: f32, restrict x: *f32, restrict y: *f32) {
var i: usize = 0 as usize;
while i < n {
y[i] = a * x[i] + y[i];
i = i +% (1 as usize);
}
return;
}
restrict is only valid on *T params (other shapes fire E0411), and is C ABI compatible (an export extern fn exports the same C signature with or without it). The raw pointer write y[i] = ... is bare: a raw index is self-flagging in the syntax, so it carries no enclosing marker.
What the compiler checks, and what it trusts
C+ ownership is boundary-checked, not whole-program inferred. It is worth knowing exactly where a rule is enforced and where you are trusted.
Enforced by the compiler:
- Use after move: reading a value after a
takeconsumes it is E0335. - Escaping a bare borrow: a bare parameter that is returned, stored, or re-passed to a
takeslot is E0337 — mark ittakeor.clone()it. - Aliasing XOR mutation: a place has shared borrows or one exclusive borrow, never both (see the borrow checker).
- Partial move out of a
Droptype: rejected (E0509), since the destructor frees fields by hand and stealing one would double free. - Returned views: a
str/T[]result, or aTextcoerced to itsstrview, must come from a parameter orstaticdata, never from a local that drops at return (E0513).
Trusted to you (the escape hatches):
- A
str/T[]view stored into a longer-lived place. These areCopyviews, not tracked references. The compiler checks the function boundary, but once you copy a view into a field or another binding it no longer tracks that the backing storage outlives it. - Raw pointers (
*T). Completely outside the borrow checker. A raw dereference*p/p[i]is self-flagging in the syntax — the act of writing the operation is where you take on the validity obligation.
One rule covers all of it: a borrow, a view, or a raw pointer must not outlive the value it points into. The compiler proves this at the enforced cases; everywhere else it is a contract you keep, and the self-flagging syntax marks where you signed up for it.
Next
Continue with the borrow checker.