Documentation / Functions

Functions

Functions stay named, statically checked and ordinary. Their signatures show the values they need, the access they request and every value they can return.

No hidden closure captures or distant conversion rules exist. The call stays readable from the source.

Choose the explanation level: Function declarations

Function declarations

A function gives a reusable block of code a name.


    greet |name String|:
        io.line([: Hello, [name]])
    ;
    

The function name comes first. Parameters live inside |...|.

Use || when there are no parameters:


    say_hello ||:
        io.line("Hello")
    ;
    

Add an arrow when the function returns a value:


    double |value Int| -> Int:
        return value * 2
    ;
    

The colon opens the body. The semicolon closes it.

A source function forms a named top-level declaration.


    greet |name String|:
        io.line([: Hello, [name]])
    ;

    double |value Int| -> Int:
        return value * 2
    ;
    

Declaration shape


        name |parameters|:
            body
        ;

        name |parameters| -> ReturnType:
            body
        ;
    
  • The function name comes first.
  • Parameters are enclosed by |...|.
  • || declares a function with no parameters.
  • -> introduces one or more return slots.
  • Omit the arrow only when the function has no return slots at all.
  • A function with only an error slot still writes -> Error!.
  • Void is not written as a return type.
  • : opens the body and ; closes it.
  • Function names use regular_snake_case.

Functions with required success values must return or otherwise terminate on every reachable path. Terminality is validated before HIR lowering.

Generic functions

Declaration-site generic parameters appear after the function name.


    identity type A |value A| -> A:
        return value
    ;
    

Generic inference and bounds are documented by Generics.

Function values

Source functions are declarations, not ordinary values.

General closures, anonymous function values, generic function values and higher-order polymorphism are outside the current language design scope. Reactivity is the constrained UI-oriented mechanism for many template cases that would otherwise lean on closures.

Choose the explanation level: Parameters and defaults

Parameters and defaults

Parameters describe the values a function needs.


    greet |name String, excited Bool|:
        if excited:
            io.line([: Hello, [name]!])
        ;
    ;
    

Each parameter has a name and a type.

A default makes a parameter optional at the call site:


    describe |prefix String = "item", subject String| -> String:
        return [prefix, ": ", subject]
    ;

    label = describe(subject = "apple")
    

The call supplies subject by name and keeps the default prefix.

Every function parameter has a name and an explicit type.


    describe |prefix String, subject String| -> String:
        return [prefix, ": ", subject]
    ;
    

Parameter forms


    name Type
    name ~Type
    name Type = default
    
  • name Type declares a shared parameter.
  • name ~Type declares a mutable or exclusive parameter.
  • = default provides a value when the caller omits that slot.
  • Parameter names follow the no-shadowing rule.
  • Each parameter name must be unique in the signature.

Reactive parameters use $Type and are documented by Reactivity.

Defaults

A default belongs to the declaration.


    describe |prefix String = "item", subject String| -> String:
        return [prefix, ": ", subject]
    ;
    

A caller can omit a defaulted parameter.

Named arguments can skip an earlier default:


    label = describe(subject = "apple")
    

A positional call cannot skip a slot in the middle. Use a named argument when the call needs to reach a later parameter.

Default value constraints

A parameter default must be a compile-time value.

Accepted:


    DEFAULT_PREFIX #= "item"

    describe |prefix String = DEFAULT_PREFIX, subject String| -> String:
        return [prefix, ": ", subject]
    ;
    

Rejected:

  • runtime bindings
  • runtime function calls
  • references to other parameters

Struct fields share the same = default syntax, but struct defaults have their own compile-time rule. Choice payload fields do not support defaults.

Choose the explanation level: Calls and access

Calls and access

Call a function by writing its name followed by arguments.


    result = double(21)
    

Arguments are positional by default. They can also name their parameter:


    label = describe(subject = "apple")
    

Positional arguments come first. Named arguments come afterward.

Mutable parameters need explicit access to an existing mutable value:


    count ~= 1
    increment(~count)
    

Fresh values do not use ~:


    increment(1)
    

Function calls use positional arguments, named arguments or both.


    render_card(title, body)
    render_card(title = title, body = body)
    render_card(title, body = body)
    

Argument routing

  • Positional arguments must come before the first named argument.
  • A positional argument is invalid after a named argument.
  • A named argument must match a real parameter name.
  • Each parameter may be supplied once.
  • Omitted defaulted parameters use their declared default.
  • Every non-defaulted parameter must receive an argument.
  • Extra positional arguments are invalid.
  • Argument values are checked and contextually coerced against their parameter types.

Named arguments can skip earlier defaults.

User functions, struct constructors, choice payload constructors and source-authored receiver methods share this routing policy.

Host functions and compiler-owned builtin member calls are positional-only.

Shared parameters

A shared parameter receives an ordinary argument without ~.


    show |value String|:
        io.line(value)
    ;

    show("hello")
    

Passing ~value to a shared parameter is invalid.

Mutable parameters

An existing mutable place needs explicit ~ at the call site.


    increment |value ~Int|:
        value += 1
    ;

    count ~= 1
    increment(~count)
    

Passing an existing place without ~ to a mutable parameter is invalid.

A fresh rvalue can satisfy a mutable parameter without source ~:


    increment(1)
    

Literals, templates, constructor calls and computed values are fresh. Writing ~ on them is invalid because ~ is place-only access syntax.

Binding mutability and call-site access are separate. See Mutable bindings.

Choose the explanation level: Returns and multiple values

Returns and multiple values

Use return to send a value back to the caller.


    double |value Int| -> Int:
        return value * 2
    ;
    

A function can return more than one success value:


    pair || -> String, Int:
        return "Ana", 2
    ;

    name, count = pair()
    

The number of receiving names must match the number of returned success values.

Functions that can fail add one final error return marked with !. The Errors page explains how to propagate or recover from that path.

return produces the success values declared by a function.


    double |value Int| -> Int:
        return value * 2
    ;
    

Success returns

  • Return values must match the declared success-slot count.
  • Each return value must be compatible with its corresponding slot type.
  • A function with required success values must return or otherwise terminate on every reachable path.
  • A function with no success slots may reach the end of its body.
  • return with no values can leave a no-success function early.
  • Void is not an explicit return-slot type.

Multiple success values

A function may declare and return more than one success value.


    pair || -> String, Int:
        return "Ana", 2
    ;

    name, count = pair()
    

The receiving multi-bind must match the success arity.

Multiple return values are not a user-visible tuple value. A normal single-target declaration cannot receive a multi-return call.

Value-producing if, full matches and catch blocks can also satisfy multi-value receiving sites when every producing path has the required arity.

Error slot shape

A function signature may contain at most one error-channel slot marked with !.

That slot must be last.


    load |id String| -> String, Error!:
        if id.is_empty():
            return! Error("Missing id")
        ;

        return "value"
    ;
    

return!, postfix propagation and catch are documented by Errors, options and assertions.

Return contract

Return slots contain types and channels only. Source code has no borrowed, owned, move or parameter-alias return annotation.

Returning an existing value follows ordinary shared-reference semantics. The compiler infers freshness and alias effects from validated bodies and calls. Public interfaces may carry those inferred summaries. External bindings may carry explicit compiler-owned alias metadata because their foreign bodies are unavailable.

The alias and freshness summary lattice is compiler-owned semantic information. It is not source syntax, a source type or a signature category.

Binding freshness and allocation identity

Every function result enters a fresh caller binding. This does not imply a fresh allocation. Returning an existing record, collection, map, string allocation, opaque handle or projection preserves its allocation identity and lifetime owner. The result binding never points at the callee's parameter or local binding slot.

Reassigning one binding does not rebind another binding that received the same allocation through a return. The allocation remains alive while any legal alias retains it.

Ordinary return does not copy. Use copy when the caller needs an independent result graph.

The compiler's inferred alias summary governs borrow and lifetime validation. It does not change the source type and does not select a binding-reference return ABI.

External alias metadata describes whether a foreign result aliases an argument allocation. It does not expose binding references to source code and does not allow host code to return a Moth binding cell.

Fallible and multiple returns

A fallible carrier is fresh while its success payload may alias a parameter allocation. Multiple return values may alias parameters or each other. Each returned value enters its own caller binding. Runtime lowering preserves those allocation relationships.