Ownership and errors¶
FP Mojo uses Mojo's ownership and error conventions. An operation's signature tells you whether it borrows or consumes a value and which errors it can raise. The same rules apply to higher-order functions: a pipeline, fold or match preserves the ownership and error types of its callbacks.
This page explains the conventions used throughout the tutorial. Each reference chapter gives the details for its operations.
Read an argument convention¶
| Spelling | Meaning in these APIs |
|---|---|
value: T |
Read-only access for the duration of the call. It is not permission to keep a reference afterwards. |
ref value: T |
A reference whose origin can flow into a returned view or reference. |
var value: T |
The callee owns the argument. Transfer a local that is not implicitly copyable with value^. |
mut value: T |
Mutable access under Mojo's exclusivity rules. |
deinit self |
A consuming method, such as Result.map, Result.raise_on_err or Choice.unwrap. |
value.copy() |
An explicit copy with the type's own copy semantics. It may allocate, or share underlying storage, as the type decides. |
ref[origin] T |
A returned reference tied to the named owner. |
A callback may declare read parameters even where the library passes it owned
values: def twice(value: Int) -> Int works as a pipeline stage, a fold step or
a map callback, and the value is destroyed after the call. Declare a parameter
var only when the callback needs to keep or consume what it receives.
Know what is borrowed, copied or consumed¶
- Borrowing APIs keep the source value and its origins. They never produce a reference that outlives the owner.
- Consuming APIs make consumption visible through
varparameters,deinitmethods or a separately named operation (fold_owned,attempt_once,fold_owned_once). Consuming an iterator is not the same as consuming the collection it borrows from. - Copying APIs require
Copyableonly when their semantics need a copy, for example the snapshots ofscan_leftor the bound arguments ofpartial. Copying always means the type's standard copy, not a deep clone. - References must have a live owner. References into consumed storage, per-call temporary copies or destroyed closures are rejected at compile time.
fp.match always borrows. A Node is immutable and shared, and a clause
receives copies of the fields it takes as they are, or the results of the ones
it asks to have evaluated. For a Choice, Result or Optional, a clause
receives a reference to the stored constructor or value. To move a value out,
use the type's consuming methods: Result.fold_owned, Choice.unwrap,
Optional.take.
For iteration, pass a native iterator or an owned collection. fold_left(add, 0,
values^) consumes the collection, as iter(values^) does, and receives owned
elements; iter(values) borrows it and yields whatever the standard iterator
yields for a borrowed collection. A collection passed without ^ is an implicit
copy, which Mojo rejects for List.
Keep callable state¶
An operation that calls a callback repeatedly keeps that one callback for the
whole operation; it does not copy the callback for each element. A composed
function owns its components: move-only components are moved in, and borrowed
components keep their origin restrictions. partial is the exception for its
arguments: it stores a copy of each bound argument and passes a fresh copy to
every call.
A reusable callable is one that stays valid for later well-typed calls. It need not be pure or return the same result each time.
Each callable protocol states how its receiver is used:
| Access | Protocol examples | Meaning |
|---|---|---|
| Shared | Unary, Binary, Thunk, BorrowCallable |
The callable is read; it can be called any number of times. |
| Exclusive | MutableUnary, MutableBinary, MutableThunk |
The callable is mutated in place; calls are sequential. |
| Consuming | OnceUnary, OnceBinary, OnceThunk, BorrowOnceCallable |
The callable is consumed by its single call. |
Argument conventions are independent of receiver access: a shared callable can consume its arguments, and a consuming callable can borrow them.
Keep failure channels apart¶
The library keeps these outcomes apart:
| Outcome | Representation | Who handles it |
|---|---|---|
| Domain failure | Result[T, E] holding Err[E] |
Result operations, matching or an explicit bridge |
| Callback or projection failure | Native raises E |
An enclosing try/except or propagation |
| No applicable clause | Not a run-time outcome: fp.match is exhaustive, and a match that leaves a constructor without a clause that cannot decline does not compile |
The compiler |
| Iterator exhausted | StopIteration raised by the source |
The iterator consumer |
| Empty uninitialized reduction | EmptyReductionError, or its ReductionError alternative |
The caller of reduce |
The same payload type can travel through different channels. A callback that
raises String differs from one that returns Err[String]. A callback that
raises StopIteration has failed; it has not ended the source. Only the
source's own exhaustion, detected outside the callback, ends iteration.
Every operation follows these rules:
- Higher-order operations propagate callback failures unchanged. They never
catch an error silently, turn it into an empty Optional or return
Errunless the operation is an explicit bridge (attempt,attempt_once,raise_on_err). - The first failure stops the operation: later stages, elements, cases and callbacks do not run.
- Error payloads keep their exact type. Nothing is converted to a string or erased.
- Non-raising callbacks add no error type. An operation that has no other
source of failure specializes to
raises Never. - Recovery does not roll back side effects that callbacks already performed. Owned values and resources are still cleaned up on every failure path.
Operations that combine several callbacks, such as matching, composition and
folds over several callbacks, need one common error type: each participant
raises that type or nothing. To combine different error types, map them
explicitly, for example into a native Variant; the library does not infer
error unions.
Write generic callbacks¶
Accumulator and element types can differ, and so can the input and output types of pipeline stages.
A predicate that selects a branch must return a scalar Bool. A SIMD mask is
never collapsed silently; reduce it yourself with the standard lane operation you
intend.
Which callable forms an operation accepts (plain functions, capturing closures,
library callables such as Partial or flow(...), adapted native signatures) is
stated in its reference chapter. The known compiler exclusions are listed in
support and limitations.
Next, start the tutorial with eager pipelines.