Skip to content

Functions

Functions are declared with the return type first, then the function name, optional generic parameters, parameters, and body.

Int add(Int left, Int right) {
return left + right;
}

Every function has a return type. Use () for Unit:

() log_done() {
print("done");
return ();
}

Parameters are written as Type name. Parameters can have default values; defaulted parameters must come at the end of the parameter list. Default values currently use literals and are checked against the parameter’s expected type:

Int scale(Int value, Int factor = 2) {
return value * factor;
}
Int result = scale(21, 2);
Int defaulted = scale(21);

Call arguments may be positional or written with name: expr. Positional arguments must appear before named arguments. Named arguments can be reordered and can skip parameters that have defaults:

Int value = scale(21, 2);
Int named = scale(value: 21, factor: 2);
Int reordered = scale(factor: 2, value: 21);
Int defaulted = scale(value: 21);

Positional arguments cannot appear after named arguments:

Int bad = scale(value: 21, 2); // error: positional argument after a named argument.

Type checking rewrites call arguments into the function signature order and fills missing defaulted parameters. MIR lowering consumes that normalized argument list and does not redo overload or default argument matching.

If several overloads share the same name, Jiang chooses the one that matches argument count, argument names, and argument types. Default parameters participate in ambiguity checks: if two overloads can both match the same call by arity and types, the declaration is ambiguous.

Int f(Int value) {
return value;
}
Int f(Int value, Int extra = 1) {
return value + extra;
}
Int a = f(10); // error: both overloads can match.

Use different names or non-overlapping parameter lists instead.

Local Declarations, Value Blocks, and Returns

Section titled “Local Declarations, Value Blocks, and Returns”
Int abs(Int value) {
if value < 0 {
return -value;
} else {
return value;
}
}

return is a statement. An explicit Unit return still writes the Unit value:

() done() {
return ();
}

Blocks can be used as value blocks. Statements do not contribute to the block value; the value comes only from the final tail expression without a semicolon. A block without a tail expression has Unit value:

Int value = {
Int base = 40;
base + 2
}
() unit_value = {
Int ignored = 1;
}

A function body is also a block, so a tail expression at the end of the body can be the function result. These two forms are equivalent:

Int add(Int left, Int right) {
return left + right;
}
Int add_tail(Int left, Int right) {
left + right
}

Use return for early returns, branch exits, or explicit Unit returns.

Use tuples for multiple returned values:

(Int, Int) split(Int value) {
return (value, value + 1);
}
Int main() {
(_ left, _ right) = split(41);
return left + right;
}

(T) is equivalent to T; a one-element tuple is not a distinct runtime shape.

Generic parameters follow the function name:

T id<T>(T value) {
return value;
}
@where(T: Numeric)
T add<T>(T left, T right) {
return left + right;
}

@where(...) is a leading annotation before the generic declaration.

Types can define instance methods and static methods:

struct User {
Int id;
static User zero() {
return User { id: 0 }
}
Int value() {
return self.id;
}
}
User user = User { id: 42 }
Int a = user.value();
User z = User.zero();

Instance methods have implicit self. static methods do not.

init(...) is a constructor with an initializing self target. It is called through Type(...) or new Type(...), and is not exposed as a normal function value. deinit() is the cleanup entry point and is not called manually as an ordinary function.

Instance calls can also be written with an explicit receiver argument:

Int a = user.value();
Int b = User.value(user$.ref());

Fn<R, A, B, ...> is a named generic type convention for function pointers: the first type argument is the return type, followed by parameter types.

Bool less(Int left, Int right) {
return left < right;
}
Fn<Bool, Int, Int> compare = less;
Bool ok = compare(1, 2);

An unbound instance method includes the receiver reference as its first parameter:

Fn<Int, User&> get_value = User.value;
Int value = get_value(user$.ref());

These examples use existing function and method names as function values; anonymous function syntax is not introduced as a day-to-day writing style yet.