Skip to content
Prismio docs
ImplementedPrismio 0.1.0

Functions and parameters

Declare Prismio 0.1 functions, return values, overloads, and borrow, sink, or inout parameters.

Last verified

Implemented. Available in the audited Prismio 0.1.0 compiler. Pre-1.0 syntax may still change.

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.

prismio
fn add(left: Int, right: Int) -> Int {
    return left + right
}

The general shape is:

text
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.

prismio
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.

prismio
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.

prismio
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: T copies scalar/copy values and borrows a move-only value by default.
  • sink value: T consumes it.
  • inout value: T takes a mutable borrow.
prismio
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.

prismio
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.

prismio
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.

prismio
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.

prismio
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.

prismio
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.

prismio
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.