Functions
fn add(x: i32, y: i32) -> i32 {
return x +% y;
}
// No return type means the unit type ().
fn shout(msg: str) {
#println(msg);
}
// Functions are public by default; a leading `_` makes one file-private.
fn answer() -> i32 { return 42; }
Every function body must end with return EXPR;. There is no implicit tail return at the function level (the rule is E0333). Block expressions can still be tail expressions inside a return or a let:
fn classify(n: i32) -> i32 {
return if n < 0 { -1 } else if n == 0 { 0 } else { 1 };
}
Generics use square brackets, not angle brackets (see Ownership for how arguments are passed):
fn identity[T](x: T) -> T { return x; }
fn max[T: Ord](a: T, b: T) -> T { ... }
There is no function overloading. A name has one signature, period. That is what lets a reader, or a model, resolve a call to exactly one definition.
Named arguments and default parameters
A parameter may declare a default value, used when the caller omits the argument:
fn drop_first(this, count: usize = 1) -> Text { ... }
let a = s.drop_first(); // count defaults to 1
let b = s.drop_first(3); // count = 3
An argument may also be passed by name, which documents intent at the call site and pairs naturally with defaulted parameters:
let name = root.value(for_key: "name");
Named arguments resolve through a type alias: type Color = vocab::Color; Color::adaptive(light: a, dark: b) finds vocab::Color's signature.
Function contracts
A function or method may declare machine-checked preconditions and postconditions. Each is a pure bool expression; the compiler emits it through the same assert path used elsewhere (a violation traps; a test build reports instead of aborting). v0.0.27 checks contracts in every build profile.
#[requires(n > 0)]
fn reciprocal(n: i32) -> i32 {
return 1 / n;
}
#[ensures(result >= n)]
fn bump(n: i32) -> i32 {
return n +% 1;
}
Rules, all compiler-enforced:
- The expression must be
booland pure (no calls, no assignments). Anything else is E0924. #[requires]is checked at entry, over parameters,thisfields, and consts. It is repeatable.#[ensures]is checked at everyreturn, after the value exists and before scope-exit drops.resultnames the returned value.- A postcondition on a function that returns nothing is allowed (it can speak about
thisandrefparameters) but namingresultthere is E0928, as is a parameter that collides with that binding. - A contract sits between the call and the
ret, so a contracted function is never a tail call.
old() state capture is not in this release.
Functions whose safety rests on a caller-side invariant
There is no unsafe keyword and no unsafe { } block. A function whose contract the compiler cannot verify is just an ordinary fn: it carries no extra marker, and its call site stays bare.
fn as_str(this) -> str { ... } // caller must uphold the invariant
let view: str = t.as_str();
What makes such an operation visible is the operation itself, not a wrapper: a raw deref or index is *p / p[i], making a pointer is x as *T, a pointer-to-int is the loud #addr(p) intrinsic, and a foreign call cannot appear without a preceding extern fn declaration. The declaration is the marker; the call stays bare. So a method like Text::as_str or a raw FFI return reads as plain C+ — the invariant lives in the function's documentation, not in syntax.