Functions
Declaring functions
Section titled “Declaring functions”Functions are declared with def. Parameters are written type name (type before name),
and the return type goes between the ) and the { (Go-style trailing return type):
def add(int a, int b) int { return a + b}
def greet(string name) { # no return type → returns no value println("hi " + name) return # value-less return for early exit}- A function call is an expression of the return type’s value, usable in expressions, assignments, and conditions.
- Using the result of a no-value function is a compile error.
Return rules
Section titled “Return rules”- In a value-returning function, the type of
return exprmust match the declared return type exactly. - Every control-flow path of a value-returning function must end in
return expr. If any path could reach the end of the body without returning, it is a compile error (the same strictness as Go and Rust).- A block definitely returns if its last statement definitely returns. An
if/elsedefinitely returns only if both branches do. Anifwithoutelse, and awhile, are not considered to definitely return.
- A block definitely returns if its last statement definitely returns. An
- A
return exprin a no-value function, and a value-lessreturnin a value-returning function, are both errors.
Forward references and recursion
Section titled “Forward references and recursion”Top-level functions are compiled in two passes: pass 1 collects every def signature,
pass 2 compiles the bodies. As a result, forward references, mutual recursion, and
self-recursion all work — a function may call another defined later in the file.
Duplicate definitions are caught in pass 1. (Global variables still must be declared
before use; only function names are order-independent.)
Calls are checked statically (exit 65) for undefined functions, argument count, and argument types. There are no implicit conversions at the call site.
Default arguments
Section titled “Default arguments”A parameter may have a default value, which must be a literal and must come after all required parameters:
def connect(string host, int port = 8080) { # ...}
connect("localhost") # port defaults to 8080connect("localhost", 3000) # port is 3000- The default value must be an
int,float,bool,string, ornullliteral — a function call, arithmetic expression, or identifier is a compile error ("default value for 'x' must be a literal"). - A required parameter cannot follow a defaulted one (
"required parameter 'y' cannot follow a default parameter"). - Default arguments apply to ordinary functions called by name. Lambdas, function values, methods, and cross-module calls use strict arity.
Named arguments
Section titled “Named arguments”Arguments may be passed by name:
def make(int x, int y) Point { return Point { x: x, y: y } }
make(x: 1, y: 2)make(1, y: 2) # positional then named is OK- Positional arguments may be followed by named ones; a positional argument after a named one is a parse error.
- Duplicate or unknown argument names are compile errors.
- Function-typed values (lambdas) have no parameter names, so named arguments do not apply to them.
First-class functions
Section titled “First-class functions”Functions are first-class values: you can store them in variables, pass them as arguments, return them, place them in struct fields, and put them in arrays.
Lambda expressions
Section titled “Lambda expressions”auto double_fn = (int x) => x * 2 # single-expression bodyauto absv = (int x) => { # block body if (x < 0) { return x * -1 } return x}auto greet = () => "hello" # no parametersFunction types
Section titled “Function types”A function type is written (T1, T2) => R:
(int) => int f = (int x) => x + 1(int, int) => bool less = (int a, int b) => a < bFunction types are structurally equal — they match on shape (parameter list plus return type), not by name.
Higher-order functions
Section titled “Higher-order functions”Functions can take and return other functions, hold them in struct fields, and collect them in arrays:
def apply(int n, (int) => int fn) int { return fn(n) }
def make_adder(int n) (int) => int { return (int x) => x + n # a closure capturing n}
struct Transform { string name (int) => int fn }
array<(int) => int> pipeline = [ (int x) => x * 2, (int x) => x + 10, (int x) => x * x]
[1, 2, 3, 4, 5].map((int x) => x * 2) # [2, 4, 6, 8, 10][1, 2, 3, 4, 5].filter((int x) => x > 2) # [3, 4, 5][1, 2, 3, 4, 5].reduce(0, (int acc, int x) => acc + x) # 15Closures
Section titled “Closures”Lambdas capture variables from the enclosing scope. Captured variables are shared: if
two closures capture the same local, a write through one is visible through the other —
this makes the make_counter pattern work. Capture is transitive across nested lambdas. A
struct method’s lambda may not capture self.
Parameter-type inference
Section titled “Parameter-type inference”In a higher-order call, the lambda’s parameter types may be omitted and inferred from the expected function type:
apply(5, (x) => x + 1) # x inferred as int from apply's (int) => int[1, 2, 3].map((x) => x * 2) # x inferred from map's element typeA mismatch with an explicit type, or a lambda with no annotation and no inferable context, is a compile error.
Calling a function value
Section titled “Calling a function value”Any function value can be called with (...), including a bare def name used as a value:
def square(int x) int { return x * x }
[1, 2, 3].map(square) # square referenced as a valuet.fn(5) # a function stored in a struct fieldarr[0](3) # a function stored in an arrayBuilt-in functions
Section titled “Built-in functions”Conversion
Section titled “Conversion”Always-succeeding conversions return a bare value:
to_string(int | float | bool) -> stringto_int(float) -> int— truncates toward zeroto_float(int) -> float
Fallible conversions return a Result<T>:
to_int(string) -> Result<int>to_float(string) -> Result<float>to_bool(string) -> Result<bool>
Result<int> r = to_int("42")if (r is ok) { println(r.value) } # 42string → int / floatparses the whole string and rejects partial matches, empty strings, and surrounding whitespace ("42 "and"42x"both error).to_int(string)also rejects integer overflow.to_bool(string)accepts only"true"/"false", case-sensitively.to_int(bool),to_float(bool),to_bool(int), andto_bool(float)are compile errors — there is no conversion path betweenbooland numbers.
to_string accepting several argument types is an internal mechanism for built-ins only;
user-defined functions cannot be overloaded (one name = one definition).
Output
Section titled “Output”println(x)— printxfollowed by a newline.print(x)— printxwith no trailing newline.
Both accept any value type with a single argument and return no value. Formatting matches
the value printer: int in decimal, float with %g plus .0, bool as true/false,
string without quotes, array<T> as [e1, e2, e3], Result<T> as ok(x) / err(msg),
and a nullable as the value or null.
String formatting
Section titled “String formatting”The string type also supports f-strings for interpolation:
string name = "world"println(f"Hello {name}!") # Hello world!For positional/indexed formatting, see std/strings’s format in the
Standard library overview.