Skip to main content
Klar

Functions

Functions are first-class values in Klar with explicit type annotations and return types.

Function Declarations

Basic Syntax

fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // function body
    return value
}

Simple Function

fn add(a: i32, b: i32) -> i32 {
    return a + b
}

fn main() -> i32 {
    let result: i32 = add(10, 20)
    return result  // 30
}

Void Functions

Functions that don't return a value use explicit -> void:

fn greet(name: string) -> void {
    println("Hello, {name}!")
}

fn main() -> i32 {
    greet("Alice")
    return 0
}

Explicit Return

All return values must be explicit. There is no implicit return of the last expression:

fn double(n: i32) -> i32 {
    return n * 2  // Explicit return required
}

// This would be an error:
// fn double(n: i32) -> i32 { n * 2 }  // Missing return

Early Return

Use return for early exit:

fn absolute(n: i32) -> i32 {
    if n < 0 {
        return -n
    }
    return n
}

Async Functions

Use async fn to declare asynchronous functions. await is only valid inside async fn bodies and requires a Future#[T] value. Future#[T] is a runtime-internal type: its memory layout is not a stable ABI and should not be used for FFI signatures or unsafe layout assumptions. For internal backend conventions, see docs/design/async-runtime-internal.md.

async fn fetch() -> Future#[i32] {
    return 21
}

async fn compute() -> Future#[i32] {
    return await fetch() + await fetch()
}

fn main() -> i32 {
    let task: Future#[i32] = compute()
    let _unused: Future#[i32] = task
    return 0
}

Generic Functions

Functions can be generic over types using #[T] syntax:

fn identity#[T](x: T) -> T {
    return x
}

fn main() -> i32 {
    let a: i32 = identity(42)       // T = i32
    let b: string = identity("hi")  // T = string
    return a
}

Multiple Type Parameters

fn swap#[T, U](a: T, b: U) -> (U, T) {
    return (b, a)
}

Trait Bounds

Constrain generic types with trait bounds:

fn max#[T: Ordered](a: T, b: T) -> T {
    if a > b {
        return a
    }
    return b
}

See Generics and Traits for more details.

Closures

Closures are anonymous functions that can capture variables from their environment.

Basic Closure Syntax

let double: fn(i32) -> i32 = |x: i32| -> i32 { return x * 2 }

let result: i32 = double(21)  // 42

Closure Type Annotation

Closure types are written as fn(ParamTypes) -> ReturnType:

let add: fn(i32, i32) -> i32 = |a: i32, b: i32| -> i32 { return a + b }

Capturing Variables

Closures can capture variables from their enclosing scope:

fn main() -> i32 {
    let factor: i32 = 5

    let multiply: fn(i32) -> i32 = |x: i32| -> i32 { return x * factor }

    return multiply(10)  // 50
}

Multiple Captures

fn main() -> i32 {
    let a: i32 = 10
    let b: i32 = 20

    let compute: fn(i32) -> i32 = |x: i32| -> i32 { return x + a + b }

    return compute(5)  // 35
}

Higher-Order Functions

Functions can take other functions as parameters:

fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
    return f(x)
}

fn main() -> i32 {
    let double: fn(i32) -> i32 = |x: i32| -> i32 { return x * 2 }
    return apply(double, 21)  // 42
}

Returning Functions

Functions can return other functions:

fn make_adder(n: i32) -> fn(i32) -> i32 {
    return |x: i32| -> i32 { return x + n }
}

fn main() -> i32 {
    let add5: fn(i32) -> i32 = make_adder(5)
    return add5(10)  // 15
}

Recursion

Functions can call themselves recursively:

fn factorial(n: i32) -> i32 {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}

fn fibonacci(n: i32) -> i32 {
    if n <= 1 {
        return n
    }
    return fibonacci(n - 1) + fibonacci(n - 2)
}

The main Function

The main function is the entry point of a Klar program:

// Basic main - returns exit code
fn main() -> i32 {
    return 0  // 0 = success
}

// With command-line arguments
fn main(args: [String]) -> i32 {
    println("Arguments: {args.len()}")
    return 0
}

// Void main (implicitly returns 0)
fn main() -> void {
    println("Hello!")
}

Parameter Passing

By Value (default)

Parameters are passed by value by default (copied):

fn increment(n: i32) -> i32 {
    return n + 1
}

By Reference (ref)

Use ref for read-only references:

fn length(ref arr: [i32]) -> i32 {
    return arr.len()
}

By Mutable Reference (inout)

Use inout for mutable references:

fn double_in_place(inout n: i32) -> void {
    n = n * 2
}

fn main() -> i32 {
    var x: i32 = 10
    double_in_place(inout x)
    return x  // 20
}

See References for more details.

Next Steps