Functions
Why This Matters
Functions are your primary API boundary for data ownership, effects, and behavior. If your function signatures are clear, the rest of the codebase stays predictable.
Basic Shape
function add(a: Integer, b: Integer): Integer {
return a + b;
}
Ownership Modes In Parameters
owned(default): function takes ownershipborrow: read-only borrowborrow mut: borrow-mut mode (caller-side exclusivity contract)
Current compiler behavior notes:
- calling
borrow mutparameters requires mutable caller binding - inside callee, parameter can be read and reassigned (
println(x),x += 1) - caller-visible mutation propagation is type-dependent in current behavior
- if you need explicit and predictable caller-visible in-place mutation semantics, use
&mut T
import std.io.*;
function consume(owned s: String): None {
println("consumed={s}");
return None;
}
function readName(borrow s: String): None {
println("read={s}");
return None;
}
mut, &, &mut At Function Boundaries
Quick rule for beginners:
- parameter
x: Treceives owned value semantics - parameter
x: &Treceives read-only reference - parameter
x: &mut Treceives mutable reference (in-place update path)
import std.io.*;
function show(x: &Integer): None {
println("x={*x}");
return None;
}
function bump(x: &mut Integer): None {
*x += 1;
return None;
}
function main(): None {
mut n: Integer = 10;
show(&n);
bump(&mut n);
println("after={n}");
return None;
}
Use this as default API design:
- read-only helper ->
&T - mutating helper ->
&mut T - ownership transfer intentionally required ->
owned T
Return Style
Use explicit return for clarity, especially in beginner-facing code and non-trivial branches.
main() Entry Constraints
Current compiler rules for main():
- no parameters
- no generic parameters
- cannot be
async - cannot be
extern - cannot be variadic
- return type must be
NoneorInteger
Higher-Order Functions
Functions are first-class values and can be passed around.
function apply(x: Integer, f: (Integer) -> Integer): Integer {
return f(x);
}
Runnable Example
import std.io.*;
function square(x: Integer): Integer {
return x * x;
}
function apply(x: Integer, f: (Integer) -> Integer): Integer {
return f(x);
}
function main(): None {
println("result={apply(7, square)}");
return None;
}
Common Mistakes
- oversized functions mixing validation, logic, and side effects
- unclear ownership in signatures when borrowing is intended
- using
borrow mutwhere&mut Tparameter would make mutation path clearer - returning sentinel values instead of explicit
Option/Resultstyle
Related
- Generics
- Ownership
- borrow-mut behavior example:
43_borrow_mut_semantics - FFI examples: