Functions
Declaration Syntax
// Function with return valuefunction name(param type [= default], ...) returns returnType // statements return valueend 'name'
// Function with no return value (implicit void)function name(param type [= default], ...) // statementsend 'name'Block Identifier: The string after end must match the function name.
Return Type: Functions that return a value must specify returns followed by the type. Functions that don’t return a value should omit the returns clause entirely.
Discarding Parameters: Use _ as the parameter name to discard an unused parameter and suppress the unused-variable error:
function onClick(_ MouseEvent) // the MouseEvent argument is intentionally unusedend 'onClick'Named Arguments
Maxon uses a first-positional, rest-named rule for function and method calls:
- First argument: Always positional. Labelling the first argument is rejected as E2052 “first arg cannot be named”.
- Subsequent arguments: Must use
name: valuesyntax (omitting the label triggers E3005) - Named arguments (after the first) can appear in any order
- Parameters with default values can be omitted
Examples:
function add(a int, b int) returns int return a + bend 'add'
add(3, b: 4) // First positional, second named
function connect(host String, port int) returns bool // ...end 'connect'
connect("localhost", port: 8080) // First positional, second named
// Single parameter functionsfunction greet(name String) print("Hello, " + name)end 'greet'
greet("Alice") // Single param is positionalDefault Values
Parameters can have default values. Parameters with defaults can be omitted at the call site. Any literal expression is supported as a default value, including integers, floats, booleans, strings, arrays, enum cases, struct construction, character literals, and byte string literals.
function greet(name String, title String = "Mr.") print("Hello, {title} {name}")end 'greet'
greet("Smith") // Uses default titlegreet("Smith", title: "Dr.") // Override default
// String defaultfunction connect(host String = "localhost") returns ExitCode // ...end 'connect'
// Array defaultfunction process(items IntArray = [10, 20, 12]) returns Integer // ...end 'process'
// Integer defaultfunction retry(attempts AttemptCount = 3) returns ExitCode // ...end 'retry'
// Float defaultfunction scale(factor ScaleFactor = 1.0) returns ScaleFactor // ...end 'scale'
// Bool defaultfunction run(verbose bool = false) returns ExitCode // ...end 'run'
// Enum defaultfunction setLevel(level Priority = Priority.medium) returns ExitCode // ...end 'setLevel'
// Struct defaultfunction draw(origin Point = Point{x: 0, y: 0}) returns ExitCode // ...end 'draw'
// Character defaultfunction setSeparator(sep Character = '/') returns ExitCode // ...end 'setSeparator'
// Byte string defaultfunction send(header ByteArray = b"HTTP/1.1") returns ExitCode // ...end 'send'Rules:
- Parameters with defaults must come after required parameters
- Default values are evaluated at call site
- Arguments may be omitted if they have defaults
- Any literal expression is supported as a default value
Function Overloads
Maxon supports function overloading — multiple functions with the same name but different signatures.
Disambiguation by Parameter Types
When overloads differ in their parameter types, the compiler automatically selects the correct overload based on the argument types at the call site:
function process(value int) returns int return value * 2end 'process'
function process(value String) returns int return value.count()end 'process'
process(42) // calls process(value int)process("hello") // calls process(value String)Disambiguation by Parameter Names
When overloads have different parameter names, the caller uses named arguments to select the correct overload:
function create(name String) returns String return nameend 'create'
function create(label String) returns String return labelend 'create'
create(name: "foo") // calls first overloadcreate(label: "bar") // calls second overloadAmbiguous Calls
If the compiler cannot determine which overload to call based on argument types alone, it requires named arguments. Calling an ambiguous overload without named arguments produces error E3007.
Examples
No Parameters
function getAnswer() returns int return 42end 'getAnswer'Void Return Type
function greet(name String) print("Hello, " + name)end 'greet'Multiple Parameters
function add(a int, b int) returns int return a + bend 'add'
var result = add(3, b: 4)Named Arguments for Clarity
function divide(dividend int, divisor int) returns int return dividend / divisorend 'divide'
var result = divide(dividend: 10, divisor: 2)Array Parameters
typealias Integer = int(i64.min to i64.max)typealias IntArray = Array with Integer
function sum(numbers IntArray) returns int var total = 0 for num in numbers 'loop' total = total + num end 'loop' return totalend 'sum'Calling Functions
First Positional, Rest Named:
var result = add(3, b: 4) // First positional, second namedvar answer = getAnswer() // No parametersgreet("Alice") // Single param is positionaldivide(100, divisor: 5) // First positional, second namedParameter Passing
Maxon uses automatic pass-by-reference for parameters that are assigned to inside the function body.
By-value (read-only parameters): If a function only reads a parameter, the value is passed directly — no indirection overhead.
By-reference (mutated parameters): If a function assigns to a parameter (directly or through a field or element), the compiler passes a pointer to the caller’s storage. This allows the called function to mutate the caller’s variable.
function increment(n int) n = n + 1 // assigns to n — passed by referenceend 'increment'
function main() returns ExitCode var x = 10 increment(x) // x is now 11 return xend 'main'Mutability rules:
- If the caller passes a
varvariable, the parameter is mutable and assignments propagate back to the caller. - If the caller passes a
letvariable to a function that mutates its parameter, the compiler raises error E3019. - If the caller passes a literal or expression (not a named variable), the compiler creates a temporary immutable stack slot. Mutations inside the function do not propagate anywhere.
function double(n int) n = n * 2end 'double'
function main() returns ExitCode var x = 5 double(x) // OK — x is var; x becomes 10
let y = 5 double(y) // ERROR E3019: cannot pass let variable to mutating parameter
double(5) // OK — literal creates a temporary; mutation has no visible effect return xend 'main'Function Types and Function-Typed Values
Functions in Maxon are first-class values: they can be stored in variables, passed as arguments, and returned from other functions. A function type is written with the function keyword and must be named via typealias — the literal function(...) returns T form is legal only as the right-hand side of a typealias declaration. Anywhere else (parameters, return types, struct fields, variable annotations, generic arguments), reference the alias by name.
typealias Score = int(i64.min to i64.max)
typealias UnaryOp = function(Score) returns Score // takes one Score, returns Scoretypealias Compare = function(Score, Score) returns bool // two Scores, returns booltypealias Callback = function() // takes nothing, returns voidThe returns clause is omitted for a void-returning function type. Once defined, the alias can be used at every use site — function parameter, return type, struct field, or generic argument:
typealias Integer = int(i64.min to i64.max)typealias UnaryOp = function(Integer) returns Integertypealias HandlerMap = Map with (String, UnaryOp)
function apply(f UnaryOp, x Integer) returns Integer return f(x)end 'apply'
function pickDouble() returns UnaryOp return double // function reference, no parensend 'pickDouble'
function main() returns ExitCode let f = pickDouble() // f has type UnaryOp return f(21) // 42end 'main'A bare function name (no parens) evaluates to a function reference. Closures (see below) and function references are both valid where a function-typed value is expected.
Closures
Closures are anonymous functions expressed inline using gives syntax:
(param) gives expression(param1, param2) gives expression() gives expressionCapture by reference: Closures capture variables from the enclosing scope by reference, not by value. This means changes to a captured variable after the closure is created are visible inside the closure when it executes.
function main() returns ExitCode var x = 10 let addX = (n int) gives n + x // captures x by reference x = 20 var result = addX(5) // evaluates with x == 20, result is 25 return resultend 'main'Notes:
- Closure parameters may optionally omit the type annotation when the type can be inferred from context.
- Closures can only appear where a function-type value is expected.
- Captured variables follow the same mutability rules as parameters: a closure that assigns to a captured
letvariable produces a compile error. - Closure parameters are checked for unused (E3012). Use
_to discard an unused parameter:(_ int) gives 42 - A closure declared inside an instance method may reference
self(and thereforeself.fieldandself.method(...)); the receiver is captured like any other local. A closure inside a free function or static method that mentionsselfis rejected with E2001.
Function Purity and Discarded Results
Maxon requires function return values to be used. The compiler infers whether each function is pure or impure and enforces different rules for discarding results.
Pure vs Impure Functions
A function is pure if it has no side effects: it does not write to stdout/stderr, does not modify global state, does not mutate parameters, and only calls other pure functions. Purity is inferred automatically by the compiler — there is no annotation.
A function is impure if it performs any side effect, either directly or by calling another impure function. Examples of impure operations include:
- Writing to stdout or stderr (e.g.,
print) - Modifying global or static variables
- Mutating parameters
- Calling runtime functions
- Calling other impure functions (transitively)
Functions with no return type are always considered impure (their result cannot be discarded because there is no result).
Discarding Pure Function Results
Pure function results must be used — they cannot be discarded, even with _ =. Since a pure function has no side effects, calling it without using the result is always a mistake.
function double(x int) returns int return x * 2end 'double'
double(5) // Error E3064: result of pure function 'double' must be used_ = double(5) // Error E3064: result of pure function 'double' must be usedlet result = double(5) // OK: result is usedDiscarding Impure Function Results
Impure function results must be explicitly acknowledged. A bare statement-level call that ignores the result is an error. To intentionally discard the result, use _ =:
var counter = 0function incrementAndGet() returns int counter = counter + 1 return counterend 'incrementAndGet'
incrementAndGet() // Error E3065: result of 'incrementAndGet' is not used_ = incrementAndGet() // OK: explicitly discardedlet count = incrementAndGet() // OK: result is usedChainable Methods
Methods that take self as their first parameter and return the same type are chainable. Their results can be freely discarded without _ =, since the common pattern is to call them for their side effect on the receiver:
type Counter var value as int
function increment() returns Counter value = value + 1 return self end 'increment'end 'Counter'
var c = Counter{value: 0}c.increment() // OK: chainable method, result can be discardedDiscarding Tuple Elements
When destructuring a tuple, individual elements can be discarded with _. If the function is pure, at least one element must be used:
var (result, _) = pureFunc() // OK: one element used(_, _) = pureFunc() // Error E3064: all elements discarded for pure functionError Codes
| Code | Meaning |
|---|---|
| E3064 | Result of a pure function must be used (cannot be discarded) |
| E3065 | Result of an impure function is not used (use _ = expr to discard) |
Extern Functions
Declare external functions (Windows API, C libraries):
extern function GetStdHandle(nStdHandle int) returns intextern function ExitProcess(uExitCode int) returns intNotes:
- No function body or
endstatement - No name mangling
- Assumes C calling convention
- Must exist at link time