Skip to content

Functions

Functions are first-class values in Nic. They can be passed as arguments, returned from other functions, and stored in variables.

Basic Functions

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

fn greet(name: string) -> unit {
    println("Hello, " + name + "!");
    return;
}

fn main() -> unit {
    let sum: i32 = add(3, 4);
    greet("World");
    return;
}

Parameters and Return Types

Every parameter must have an explicit type. The return type follows ->:

nic
fn multiply(x: i32, y: i32) -> i32 {
    return x * y;
}

fn divide(x: f64, y: f64) -> f64 {
    return x / y;
}

// No return value uses 'unit'
fn log_message(msg: string) -> unit {
    println(msg);
    return;
}

Function Types

Functions have types and can be stored in variables:

nic
fn double(x: i32) -> i32 {
    return x * 2;
}

fn main() -> unit {
    // Function type: fn(i32) -> i32
    let f: fn(i32) -> i32 = double;
    
    let result: i32 = f(21);  // 42
    return;
}

Higher-Order Functions

Functions that take or return other functions:

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

fn square(n: i32) -> i32 {
    return n * n;
}

fn main() -> unit {
    let result: i32 = apply(square, 5);  // 25
    return;
}

Lambda Expressions

Anonymous functions (closures) are written with fn:

nic
fn main() -> unit {
    // Lambda expression
    let double = fn(x: i32) -> i32 { return x * 2; };

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

    // Inline lambda
    let squared: i32 = apply(fn(n: i32) -> i32 { return n * n; }, 4);

    return;
}

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

Lambda Statement Bodies

Lambdas can have multi-statement bodies, not just single return expressions:

nic
fn main() -> unit {
    // Lambda with multiple statements
    let process = fn(x: i32) -> i32 {
        printf("Processing: %d\n", x);
        let doubled = x * 2;
        let result = doubled + 1;
        return result;
    };

    let value = process(10);  // Prints "Processing: 10", returns 21

    // Complex lambda with control flow
    let clamp = fn(val: i32, min: i32, max: i32) -> i32 {
        if val < min {
            return min;
        } else if val > max {
            return max;
        } else {
            return val;
        }
    };

    printf("Clamped: %d\n", clamp(50, 0, 100));  // 50
    printf("Clamped: %d\n", clamp(150, 0, 100)); // 100

    return;
}

Bare Function Pointers

For C interoperability, use bare function pointers that don't capture any environment:

nic
// Bare function pointer type - no closure environment
fn bare_callback(x: i32) -> unit {
    printf("Callback called with: %d\n", x);
}

// Type annotation: fn(bare, i32) -> unit
extern fn register_callback(cb: *fn(bare, i32) -> unit) -> unit;

fn main() -> unit {
    // Pass bare function as C callback
    register_callback(bare_callback);

    return;
}

Bare vs Closure Function Pointers

TypeSyntaxCaptures EnvironmentC Compatible
Closurefn(T) -> UYesNo
Barefn(bare, T) -> UNoYes

Use bare function pointers when:

  • Passing callbacks to C libraries (pthread, event handlers, etc.)
  • The function doesn't need to capture local variables
  • You need a stable function pointer for FFI

Closures Capture Variables

Lambdas can capture variables from their enclosing scope:

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

fn main() -> unit {
    let add_10 = make_adder(10);
    let result: i32 = add_10(5);  // 15
    
    return;
}

Visibility

Functions are private by default. Use pub to make them public:

nic
pub fn public_function() -> unit {
    return;
}

fn private_function() -> unit {
    return;
}

External Functions (C FFI)

Declare C functions to call from Nic:

nic
extern fn printf(format: string, ...) -> i32;
extern fn malloc(size: size_t) -> opaque;
extern fn free(ptr: opaque) -> unit;

fn main() -> unit {
    printf("Hello from C!\n");
    return;
}

Exporting to C

Export Nic functions callable from C:

nic
pub extern fn add(a: i32, b: i32) -> i32 {
    return a + b;
}
// C can call: int32_t add(int32_t a, int32_t b);

Summary

FeatureSyntax
Basic functionfn name(params) -> Type { body }
Function typefn(T1, T2) -> R
Lambdafn(params) -> Type { body }
Publicpub fn name(...) -> Type { }
Externalextern fn name(...) -> Type;
Exportpub extern fn name(...) -> Type { }

Next

Learn about Control Flow to make decisions in your code.

Released under the MIT License.