Skip to main content
Sifr gives you Rust’s memory-safety guarantees without making every call site look like Rust. Function parameters borrow by default, parameter mutation is explicit, and ownership transfer is opt-in. Most code starts with a plain parameter and reaches for mut, own, or own mut only when the function’s contract needs it.

Parameter Conventions

This table is the core model. A bare heap parameter is borrowed immutably. mut lets the function update the caller’s value during the call. own moves the value into the function. own mut moves it and lets the function change it. The same conventions apply to class methods. In particular, a bare move-only method parameter is borrowed and cannot be returned or stored as an owned value; spell it own when the method intentionally consumes it.

Borrow By Default

When you pass a heap value to a function, Sifr borrows it by default. The function can read the value, and the caller keeps ownership.
Bare parameters are immutable. The callee cannot reassign the parameter name or mutate the object behind it.

mut For Mutable Borrows

Use mut when the function should update the caller’s value in place.
mut is required both for rebinding a parameter name and for mutating through a heap parameter. Rebinding a mutable borrowed parameter to another value still does not make the borrowed value owned.
A mutable borrow cannot escape. mut items: list[int] can update the caller’s list during the call, but it cannot return items or store it as an owned value. Use own, own mut, or items.clone() depending on whether the function should consume, mutate, or copy the value.

own And own mut

Use own when a function consumes a value. After the call, the caller cannot use the moved value.
Use own mut when the function consumes a value and needs to mutate it.
own mut is the canonical spelling. The formatter rewrites mut own to own mut.

Scalars And Immutable Values

Scalar values such as int, float, and bool behave like ordinary values at the source level. A mut n: int parameter permits local rebinding inside the function, but it does not create an observable heap borrow or ownership transfer.
Some types are immutable by design. For example, mut does not make bytes subscript assignment legal.

Across await

Mutable borrows cannot remain live across an await point. End the mutation before awaiting, move owned data into a task, or wrap shared state in an explicit synchronization primitive.
For the task-boundary model, see Concurrency.

Rules At A Glance

Coming from Python?

Follow a path through mutation, typed errors, imports, and packages.

Coming from Rust?

Map mut, own, and borrow-by-default rules back to Rust concepts.