Functions and parameters
Declare Prismio 0.1 functions, return values, overloads, and borrow, sink, or inout parameters.
Last verified
A function has a name, zero or more typed parameters, an optional return type, and a block body. Functions are top-level declarations; Prismio 0.1 does not nest named functions inside blocks.
fn add(left: Int, right: Int) -> Int {
return left + right
}The general shape is:
fn name([mode] parameter: Type, ...) [-> Type] {
statements
}Parameter types are required. The compiler does not infer a public signature from call sites. A trailing -> Type declares a returned value; omitting it declares a function that returns no value.
Calling a function
A call evaluates its arguments and transfers or borrows them according to the selected function signature.
import std.io
fn clamp_low(value: Int, minimum: Int) -> Int {
if (value < minimum) { return minimum }
return value
}
fn announce(value: Int) {
println(value)
}
fn main() -> Int {
let result = clamp_low(-3, 0)
announce(result)
return result
}The number of arguments and their exact types must match an available declaration. There are no named arguments, default arguments, or automatic numeric conversions.
Return rules
Omitting -> Type declares a no-value-returning function. Non-void functions must return on every reachable path. Code after an unconditional return is rejected as unreachable.
fn log_ready() {
println("ready")
return
}For a value-returning function, return expression must match the declared return type. Prismio does not use a final expression as an implicit return.
fn classify(value: Int) -> Int {
if (value > 0) {
return 1
}
}
fn main() -> Int { return classify(1) }The compiler rejects classify because the path where value <= 0 reaches the end without returning an Int. An infinite loop or a return on every branch can satisfy definite-return analysis.
An unconditional return, break, or continue also ends the reachable statements in its local control-flow position. The compiler reports statements that can never execute rather than silently emitting them.
Parameter modes
value: Tcopies scalar/copy values and borrows a move-only value by default.sink value: Tconsumes it.inout value: Ttakes a mutable borrow.
struct Counter { value: Int }
fn read(counter: Counter) -> Int { return counter.value }
fn increment(inout counter: Counter) { counter.value = counter.value + 1 }
fn finish(sink counter: Counter) -> Int { return counter.value }The caller writes no separate borrow operator. The callee's signature determines the mode.
Default parameters borrow move-only data
An ordinary parameter does not consume a string, list, or struct. It creates a callee-visible borrow for the duration of the call. The caller retains ownership and may use the value afterward.
struct Message { code: Int }
fn inspect(message: Message) -> Int {
return message.code
}
fn main() -> Int {
let message = Message { code: 7 }
let code = inspect(message)
return message.code - code
}The callee cannot drop or move a borrowed parameter into an owned location. Make consumption explicit with sink when the callee needs ownership.
sink transfers ownership
sink documents a consuming API at the function boundary. Passing a move-only value marks the caller's binding as moved.
struct Message { code: Int }
fn consume(sink message: Message) -> Int {
return message.code
}
fn main() -> Int {
let message = Message { code: 7 }
consume(message)
return message.code
}The last field access is a use after move. sink on a copy type is accepted as a signature mode where supported, but the observable value behavior remains copy-like; it is primarily meaningful for move-only values.
inout permits caller-visible mutation
inout forms an exclusive mutable borrow. It does not move the value, and changes made through the parameter are visible after the call.
struct Counter { value: Int }
fn increment(inout counter: Counter) {
counter.value = counter.value + 1
}
fn main() -> Int {
let counter = Counter { value: 4 }
increment(counter)
return counter.value - 5
}There is no &, &mut, or address-of spelling at the call site. The selected parameter declaration provides the borrowing contract.
Recursion
Functions may call themselves and other declared functions. Recursive code must still satisfy ordinary return and ownership rules.
fn factorial(value: Int) -> Int {
if (value <= 1) { return 1 }
return value * factorial(value - 1)
}
fn main() -> Int {
return factorial(5) - 120
}Tail-call optimization is not a source-language guarantee. Bound recursion explicitly when stack usage matters.
Overloading
Functions may share a name when their parameter count or exact parameter types differ. Resolution does not use implicit numeric coercions, and return type alone cannot distinguish overloads. Default arguments, named arguments, variadic functions, and expression bodies are not implemented. Functions may be generic.
fn describe(value: Int) -> Int { return value }
fn describe(value: U64) -> U64 { return value }
fn describe(left: Int, right: Int) -> Int { return left + right }At a call site, the compiler filters by arity and exact parameter types. If no candidate matches, it reports the mismatch. If more than one candidate is equally applicable, it reports ambiguity rather than choosing from the return context.
fn measure(value: Int) -> Int { return value }
fn main() -> Int {
let value: U8 = 2
return measure(value)
}Write measure(value as Int) to select the Int overload deliberately.
Entry point
An executable uses a top-level main function. The common portable form is fn main() -> Int, where the returned integer becomes the process exit status. The 0.1 compiler does not document command-line argument parameters on main; obtain environment or argument data through supported runtime or FFI functions instead of copying signatures from C, Rust, or older drafts.
Not implemented
Named functions cannot currently be nested, define optional/default arguments,
accept source-level variadics, or use expression bodies. Functions may declare
type parameters with one or more trait bounds.
Closures provide anonymous capturing callables, and method-call syntax plus impl
blocks provide type-oriented operations; each has its own documented constraints.