Functions
Functions are first-class values in Nic. They can be passed as arguments, returned from other functions, and stored in variables.
Basic Functions
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 ->:
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:
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:
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:
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:
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:
// 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
| Type | Syntax | Captures Environment | C Compatible |
|---|---|---|---|
| Closure | fn(T) -> U | Yes | No |
| Bare | fn(bare, T) -> U | No | Yes |
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:
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:
pub fn public_function() -> unit {
return;
}
fn private_function() -> unit {
return;
}External Functions (C FFI)
Declare C functions to call from 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:
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
| Feature | Syntax |
|---|---|
| Basic function | fn name(params) -> Type { body } |
| Function type | fn(T1, T2) -> R |
| Lambda | fn(params) -> Type { body } |
| Public | pub fn name(...) -> Type { } |
| External | extern fn name(...) -> Type; |
| Export | pub extern fn name(...) -> Type { } |
Next
Learn about Control Flow to make decisions in your code.