A type projection names a type computed from a function declaration.
| Form | Meaning |
|---|---|
function::result |
Success or ordinary return type |
function::err |
Error type |
function::name |
Useful data type of the local binding named name |
Projections are written only in type positions.
They are compile-time references, not runtime reflection. The compiler waits until the target function’s signature and body types are finalized, then replaces the projection with the resolved type.
Results
::result refers to the function’s successful return type.
fn load_numbers(): []int {
return [1, 2, 3]
}
struct Cache {
values: load_numbers::result
}The projection resolves after return inference. It therefore works for an explicit return and for a function whose result is inferred.
For a fallible function, ::result unwraps the success side.
fn load_user(id: int): User ! LoadError {
// ...
}
type LoadedUser = load_user::resultLoadedUser is User, not Result[User, LoadError].
Errors
::err names a function’s error type.
type UserLoadError = load_user::errApplying ::err to an infallible function is an error because there is no
error side to project.
This is useful for adapters that should track a lower-level API’s error contract without manually duplicating a moving union.
For a declared or inferred error union, ::err denotes the complete union,
not the error type of only the first failing call.
Bindings
function::name projects a named local binding from the function.
model User {
id: int
name: string
}
fn fetch_users(): []User ! QueryError {
rows := FROM User?
return rows
}
type Row = fetch_users::rowsA binding projection peels a list or optional wrapper to expose the useful
element shape. In the example, rows has type []User, while
fetch_users::rows resolves to User.
The current peeling rules are:
| Binding type | Projected type |
|---|---|
Result[T, E] |
first continue with T |
Option[T] or T? |
T |
List[T] or []T |
T |
Set[T] |
T |
Map[K, V] |
V |
another type T |
T unchanged |
After peeling Result, one collection or optional wrapper is removed. A map
projects its value shape, not its key or (K, V) entry shape.
This behavior is designed for query-generated records.
fn summaries(): void ! QueryError {
rows := FROM User SELECT { id, name }?
}
type Summary = summaries::rowsSummary names the compiler-generated { id, name } row shape without
copying its definition.
Qualified query fields
A projection generated from a join retains the types resolved from each qualified model field.
fn capacities(): void ! QueryError {
rows := FROM Booking
JOIN Schedule ON Booking.schedule_id == Schedule.id
SELECT { capacity: Schedule.capacity }?
}
type Capacity = capacities::rowsThe compiler resolves Schedule.capacity against Schedule, then records
that field in the generated record.
Failures
The compiler diagnoses:
- an unknown function;
- a missing binding name;
::erron an infallible function;- a projection used where a value is expected;
- a projected type that cannot be finalized because the source declaration itself is invalid.
Binding lookup applies to a named local binding in the target body. A destructured tuple or record does not provide one aggregate binding name to project. Name the component explicitly or return a named type when it is part of a stable contract.
Coupling
A projection intentionally couples one declaration’s type to another function’s inferred body or query shape. This is useful inside a module, but it can make a broad public API sensitive to local renaming.
Use a named struct for a durable external contract. Use a projection when tracking the compiler-derived shape is the desired behavior.
Changing the target’s return annotation, fallibility, local name, collection wrapper, or query projection can change consumers at compile time. That sensitivity is the purpose of the feature, but it is also why projections are best kept within a closely maintained module boundary.