Skip to content

Functions & Closures

Functions in Chuks are declared with the function keyword and support type annotations:

function add(a: int, b: int): int {
return a + b
}
println(add(3, 4)) // 7

Return types are specified after the parameter list with a colon:

function greet(name: string): string {
return `Hello, ${name}!`;
}
function doWork(): void {
println("Working...");
}

Parameters can have default values, making them optional:

function createUser(name: string, age: int = 25): void {
println(name + " is " + age + " years old");
}
createUser("Alice"); // Alice is 25 years old
createUser("Bob", 30); // Bob is 30 years old

Closures are anonymous functions defined inline. They capture variables from their enclosing scope:

// Lambda syntax
const double = (x: int): int => x * 2;
println(double(5)); // 10
// Multi-line closure
const process = (items: []int): []int => {
var result: []int = [];
for (item of items) {
result.push(item * 2);
}
return result;
}

Functions can accept other functions as arguments and return functions:

function apply(fn: (int) => int, val: int): int {
return fn(val);
}
function multiplier(factor: int): (int) => int {
return (x: int): int => x * factor;
}
const triple = multiplier(3);
println(apply(triple, 10)); // 30

When declaring function type annotations, you can include parameter names for clarity. The syntax is function(name: type, ...): returnType.

// Function type with named parameters
var greet: function(name: string): string = function(name: string): string {
return "Hello, " + name;
}
println(greet("World")); // Hello, World

Named parameters make callback signatures easier to read:

// Named params in callback type
function applyOp(a: int, b: int, op: function(x: int, y: int): int): int {
return op(a, b);
}
var sum = applyOp(10, 5, function(x: int, y: int): int {
return x + y;
});
println(sum); // 15

Function types without names (positional) are also supported:

var double: function(int): int = function(n: int): int {
return n * 2;
}
println(double(21)); // 42

Return function types with named parameters for self-documenting code:

function makeAdder(n: int): function(x: int): int {
return function(x: int): int {
return x + n;
}
}
var add5: any = makeAdder(5);
println(add5(10)); // 15

A function-typed variable or field can be made nullable with the function? syntax. This declares that the value is either a callable function or null.

var handler: function?(msg: string): void = null
if (handler == null) {
println("no handler set")
}
handler = function(msg: string): void {
println("received: " + msg)
}
handler("hello") // received: hello

The ? goes directly after the function keyword — not after the return type. This makes it clear that the function itself is nullable, not the return value.

Nullable function types are useful for optional callbacks:

function runCallback(cb: function?(): void): void {
if (cb != null) {
cb()
} else {
println("no callback")
}
}
runCallback(null) // no callback
runCallback(function(): void { println("ran") }) // ran

Classes can store nullable function fields for event handlers, hooks, and configurable behavior:

class EventEmitter {
private var onMessageHandler: function?(msg: string): void = null
private var onCloseHandler: function?(): void = null
public onMessage(h: function?(msg: string): void): void {
this.onMessageHandler = h
}
public onClose(h: function?(): void): void {
this.onCloseHandler = h
}
public emit(msg: string): void {
if (this.onMessageHandler != null) {
this.onMessageHandler(msg)
}
}
public close(): void {
if (this.onCloseHandler != null) {
this.onCloseHandler()
}
}
}
var emitter = new EventEmitter()
emitter.onMessage(function(msg: string): void {
println("msg: " + msg)
})
emitter.emit("hello") // msg: hello
// Reset handler to null
emitter.onMessage(null)

Nullable functions can have any return type, including nullable return types:

// Nullable function that returns int
var transform: function?(x: int): int = null
transform = function(x: int): int {
return x * 2
}
println(transform(21)) // 42
// Nullable function that returns int?
var lookup: function?(key: string): int? = null
lookup = function(key: string): int? {
if (key == "age") { return 25 }
return null
}

Nullable function types work with generic type parameters:

class Processor<T> {
private var handler: function?(msg: T): void = null
public setHandler(h: function?(msg: T): void): void {
this.handler = h
}
public process(data: T): void {
if (this.handler != null) {
this.handler(data)
} else {
println("no handler")
}
}
}
var p = new Processor<string>()
p.process("test") // no handler
p.setHandler(function(msg: string): void {
println("got: " + msg)
})
p.process("test") // got: test

Functions that perform asynchronous work are declared with async and return a Task<T>:

async function fetchData(url: string): Task<string> {
const resp = await http.get(url);
return resp.body;
}

See the Concurrency guide for details on async/await and spawn.