hd-lang

#Functions

Status: language specification draft.

This chapter defines functions, hd-lang's primary unit of behavior.

  1. fn.kind.ordinaryMethods, entry points, tests, tools, workflows, and generated adapters are ordinary functions with additional library metadata or calling conventions.
fn square(n: i32) -> i32: n * n

fn demo() -> i32:
    square(3) + square(4)   # 25

#Declarations

A named function declares typed parameters and a result type.

fn add(a: i32, b: i32) -> i32:
    a + b

fn load_user!(id: UserId) -> Result[User, DbError] $ Database:
    ...
  1. fn.decl.suspendingA name ending in ! declares a suspending function.
  2. fn.decl.requirementsA trailing $ clause declares requirements.
  3. fn.limit.complexityAn implementation may limit the work of checking one item, such as a function body. The limit is implementation-defined.
  4. fn.limit.complexity.errorAn item whose check exceeds that limit is an error, reported on the item. Error: item-too-complex.

#Parameter And Result Types

  1. fn.decl.parameter-typesNamed functions must declare every parameter type.
  2. fn.decl.result-required-pubA public function, a public inherent method, a trait method, and a method of a trait implementation must also declare its result type. Omitting it is an error. Error: missing-result-type.
  3. fn.decl.result-omitted-privateA non-public function, a non-public inherent method, or a local fn declaration may omit -> type.
  4. fn.decl.result-inferredIts result type is then inferred from its body, as for a nonrecursive closure.
  5. fn.decl.result-inferred.commonThe inferred result is the least common type of the body's final value and every return operand.
  6. fn.decl.result-inferred.voidThe inferred result is void when the body produces no value.
  7. fn.decl.row-omittedSuch a function may also omit its requirement clause. Its row is then inferred as specified in Requirements and Suspension.
  8. fn.decl.suspension-not-inferredSuspension is never inferred: it is part of the function's name.
Error example
pub fn identity(x: i32): x  # error: missing-result-type

struct Cart:
    count: i32

impl Cart:
    pub fn total(self):  # error: missing-result-type
        self.count

Why. Every signature that another file can see is written out. So a caller in another file never waits for a callee's body to be checked. Files can then be checked in parallel.

#Opaque Results

  1. fn.opaque.named-functionAny named function, including a trait declaration or implementation method, may declare an opaque result.
  2. fn.opaque.nestedA return annotation may contain multiple opaque type occurrences, including nested type arguments, tuples and optional types.
  3. fn.opaque.occurrence-same-typeFor each opaque occurrence, every value supplied at that occurrence must have the same hidden concrete type. Error: type-mismatch.
  4. fn.opaque.occurrence-no-joinDistinct hidden types at one occurrence are not joined or converted to choose a common hidden type.
  5. fn.opaque.occurrence-boundEach hidden concrete type must satisfy its occurrence's declared trait bound and associated-type bindings. Error: unsatisfied-trait-bound.

#Recursion Without A Result Type

  1. fn.decl.omitted-recursionA function whose result type is omitted must not be recursive through functions whose result types are all omitted, itself included.
  2. fn.decl.omitted-cycleA cycle of functions with omitted result types that call each other, directly or through one another, is an error. Error: recursive-function-needs-result-type.
  3. fn.decl.omitted-cycle.reportThe error is reported once, on the member of the cycle that appears first in source order.
  4. fn.decl.omitted-cycle.resolveAdding a result type to any member of the cycle resolves it.
Error example
fn is_even(n: i32):  # error: recursive-function-needs-result-type
    if n == 0: true
    else: is_odd(n - 1)

fn is_odd(n: i32):
    if n == 0: false
    else: is_even(n - 1)

#Bodies And Control Paths

  1. fn.body.final-valueThe body's normal final value must be assignable to the declared or inferred result.
  2. fn.body.early-returnExplicit return may complete the function earlier.
  3. fn.body.pathsEvery reachable control path must do one of three things. It returns a value assignable to the declared result, falls through with such a final value, or completes abruptly by propagation or panic.
  4. fn.body.value-less-fallthroughA non-void function with a reachable value-less fallthrough is an error. Error: missing-return-value.
  5. fn.body.infinite-loopA path that enters an infinite loop that no break targets never completes, so it needs no value.
  6. fn.body.void-finalA void function likewise rejects a non-void final expression.
Error example
fn invalid(flag: bool) -> i32:
    if flag:  # error: missing-return-value
        return 1

Note. Use return, pass, or another void expression when a preceding value is intentionally ignored.

#Function Names

  1. fn.name.uniqueFunction names are unique in their scope.
  2. fn.name.no-overloadinghd-lang has no function or method overloading: one name resolves to one declaration in its scope.
Error example
fn value(input: i32) -> i32: input

fn value(input: string) -> string: input  # error: duplicate-module-name

#Same-Line Bodies

  1. fn.body.same-lineA same-line body is permitted when it contains one simple statement.
fn test() -> void $ Console: println("hi")

#Local Functions

A named function may also be declared inside an executable block suite:

fn total_with_bonus(values: List[i32], bonus: i32) -> i32:
    fn add_bonus(value: i32) -> i32:
        value + bonus

    let total = 0
    for value in values:
        total = total + add_bonus(value)
    total
  1. fn.local.declareA named function may be declared inside an executable block suite.
  2. fn.local.scopeIts name is visible from that declaration onward and within its own body.
  3. fn.local.capture-rulesIt can capture enclosing local values under the same capture rules as a closure.
  4. fn.local.privateIt cannot be marked pub or used from another module.

See also: Captures.

#Parameters

This section defines how parameters are initialized and passed.

fn inspect(user: User) -> string: user.name

fn rename(user: &mut User, name: string) -> void:
    user.name = name
  1. fn.param.init-orderParameters are evaluated and initialized from left to right after argument mapping.
  2. fn.param.fixedA parameter that is not mut is a non-reassignable binding: its name cannot be reassigned. Reassigning one is an error. Error: non-reassignable-parameter-binding.
  3. fn.param.primitivePrimitive parameters pass by value.
  4. fn.param.value-copyEvery other parameter that is not a reference passes by value too. The callee gets its own copy of a struct, tuple, enum, list, or map argument.
  5. fn.param.referenceA &T parameter passes a reference, through which the callee reads the caller's place or object. Through a &mut T parameter it may also write.
  6. fn.param.no-transferhd-lang does not perform ownership transfer at a call.
  7. fn.param.mutmut before a parameter name, as in mut a: S, marks the callee's own copy of the argument, which may change.
  8. fn.param.mut.copyThe argument moves into a mut parameter at the caller's last use of it, and is copied otherwise. The caller's value is unchanged unless the callee returns the copy and the caller keeps it.
  9. fn.param.mut.reassignA mut parameter may be reassigned as a whole, and a struct field of one is a writable place.
  10. fn.param.mut.viewmut a: T marks the binding, not the type, so mut a: S with a struct S is valid and a: mut S is an error. Error: mut-on-struct.
  11. fn.param.mut.closureA closure parameter may be mut in the same way.
Error example
fn normalize(value: i32) -> i32:
    value = 0  # error: non-reassignable-parameter-binding
    value
struct Counter:
    n: i32

fn bumped(mut c: Counter) -> Counter:
    c.n += 1   # changes the copy, which is returned
    c

fn bump(mut c: Counter) -> void:
    c.n += 1   # changes only the copy: the caller's value is unchanged

Note. A mut parameter never changes the caller's value in place. A reference parameter, &mut T, does; Values And References states the syntax.

#Positional And Named Arguments

A call may mix positional and named arguments:

resize(640, height=480)
  1. fn.arg.mixCalls may mix positional and named arguments.
  2. fn.arg.positional-firstPositional arguments must come first.
  3. fn.arg.positional-after-namedA positional argument must not follow a named argument.
  4. fn.arg.labelsNamed argument labels are parameter names and are part of the source-level calling interface.
  5. fn.arg.unknown-nameA named argument that names no parameter is an error. Error: unknown-named-argument.
  6. fn.arg.onceA parameter must receive one value, either explicitly or from its default.
  7. fn.arg.duplicateSupplying a parameter more than once is an error. Error: duplicate-argument.
Error example
fn resize(width: i32, height: i32) -> i32: width * height

fn area() -> i32: resize(640, depth=480)   # error: unknown-named-argument
fn twice() -> i32: resize(640, width=480)  # error: duplicate-argument

#Default Values

A parameter may declare a default:

fn connect(host: string, port: i32 = 443, tls: bool = true) -> Connection:
    ...
  1. fn.default.allowedA parameter may declare a default.
  2. fn.default.order-final-functionAfter the first parameter with a default, every following non-vararg parameter must also have a default. The one exception is a final parameter whose type is a function type. A later parameter without one is an error. Error: default-order.
  3. fn.default.omitCalls may omit only parameters that have defaults.
  4. fn.default.evalDefaults are evaluated for each call, in parameter declaration order, after all explicit argument expressions have been evaluated.
  5. fn.default.scopeA default may refer to earlier parameters but not later parameters.
  6. fn.default.later-parameterA default that names a later parameter is an error. Error: binding-not-yet-visible.
  7. fn.default.final-functionA call may supply such a final function-typed parameter by a trailing block or a named argument, and omit the defaulted parameters before it.
  8. fn.default.observable-effectsThe observable effects of a default must occur on every call that omits its argument, even when its result is constant.
fn retry(times: i32, backoff: i32 = 100, body: fn() -> void) -> void:
    body()

fn run() -> void:
    retry(3):
        pass
    retry(3, backoff=10):
        pass
    retry(3, body=fn(): pass)

Why. As in Kotlin and Swift, defaulted options may come before a trailing body. A trailing block or a named argument always reaches the final parameter.

#Requirement-Free Defaults

  1. fn.default.requirement-freeA default expression must be requirement-free: it must not use a provider and must not suspend.
  2. fn.default.requirement-free.syntaxConcretely, it must not contain $.use, a provider scope, or a bang call.
  3. fn.default.requirement-free.callsEvery function, method, closure, or function value it calls must have an empty requirement row.
  4. fn.default.requirement-free.signaturesThe check reads only signatures, never function bodies, because both properties are part of every callable's type.
  5. fn.default.requirement-free.uniformThe check applies equally to named functions, function values, and dynamic trait methods.
  6. fn.default.effectsA default may otherwise evaluate any expression, including calls that mutate state.
  7. fn.default.providerA default that uses a provider, in a parameter, struct field, or shared enum constructor parameter, is an error. Error: requirement-in-default.
  8. fn.default.suspendsA default that suspends is an error. Error: suspension-forbidden-context.
Error example
trait Clock:
    fn now(self) -> i32

fn current_time() -> i32 $ Clock:
    $.use(Clock).now()

fn wait!() -> i32: 1

fn bad(first: i32 = 1, second: i32) -> i32: second                     # error: default-order
fn span(start: i32 = finish, finish: i32 = 10) -> i32: finish - start  # error: binding-not-yet-visible
fn stamp(time: i32 = current_time()) -> i32 $ Clock: time              # error: requirement-in-default
fn late(value: i32 = wait!()) -> i32: value                            # error: suspension-forbidden-context

Note. Because defaults run after all explicit arguments, in parameter declaration order, the effects of a default are ordered.

#Varargs

A final positional parameter may write ... after its name. It collects the call's remaining positional arguments into its declared type:

fn sum(values...: List[i32]) -> i32:
    ...
  1. fn.vararg.formA final positional parameter may write ... after its name, as in values...: List[i32]. It is then a vararg.
  2. fn.vararg.typeThe type after : is the parameter's type inside the body, as for any parameter, so values above is a List[i32].
  3. fn.vararg.type.kindsA vararg whose type is not List[T], a tuple type, or a type parameter bounded by Tuple is an error. Error: type-mismatch.
  4. fn.vararg.collectA call supplies a vararg zero or more separate positional arguments, which are collected into its type, or one positional spread of that type.
sum()
sum(1, 2, 3)
sum(items...)
  1. fn.vararg.collect.listFor List[T], each argument is checked against T, and the list holds the arguments in order.
  2. fn.vararg.collect.tuple-exprFor a tuple type, the arguments are collected exactly as the tuple expression of them, against that type, by Tuple Expressions and Tuple Rest Elements. So call(g, 1, 2, xs...) collects (1, 2, xs...). Error: type-mismatch.
  3. fn.vararg.tuple-param.expectedAnother argument may solve the vararg's Tuple-bounded type parameter, as f solves Args below. Then the vararg's arguments are checked against that solved tuple as an expected type.
  4. fn.vararg.tuple-param.inferWhen no other argument solves it, inference solves it as the tuple of the argument types, one element per argument, with no join.
  5. fn.vararg.no-auto-spreadA tuple argument is never spread automatically: it is one element of the collected tuple.
use std.function.{Fn, Tuple}

fn add2(a: i32, b: i32) -> i32: a + b
fn neg(a: i32) -> i32: -a
fn zero() -> i32: 0
fn g(a: usize, b: usize, xs...: List[i32]) -> usize: a + b + xs.len()

fn call[Args < Tuple, O, $R](f: Fn[Args, O, $ R], args...: Args) -> O $ R:
    f(args...)

fn pack[Args < Tuple](args...: Args) -> Args: args

fn run(pair: (i32, i32)) -> i32:
    call(add2, 1, 2) + call(neg, 5) + call(zero) + call(add2, pair...)

fn tail(xs: List[i32]) -> usize: call(g, 1, 2, 3, 4) + call(g, 1, 2, xs...)
Call Args
call(add2, 1, 2) (i32, i32), from add2
call(neg, 5) (i32,)
call(zero) ()
call(add2, pair...) (i32, i32), which the spread matches
call(g, 1, 2, 3, 4) (usize, usize, List[i32]...), from g; 3 and 4 form the rest
call(g, 1, 2, xs...) (usize, usize, List[i32]...), from g; xs is the rest
call(add2, (1, 2)) (i32, i32), which the one argument (1, 2) does not fill
pack(1, "a") (i32, string), from the arguments
  1. fn.vararg.finalA vararg that is not the last positional parameter is an error. Error: nonfinal-vararg.
  2. fn.vararg.no-defaultA vararg has no default expression.
  3. fn.vararg.namedPassing a vararg by name passes its collected value without spread syntax, as in sum(values=[1, 2]) or call(add2, args=(1, 2)).

A function value keeps a List[T] vararg, as Vararg Inputs states:

fn count(values...: List[i32]) -> usize: values.len()

fn main() -> usize:
    f := count
    f(3, 4, 5)
Open in playground

Here f has type fn(List[i32]...) -> usize.

Error example
use std.function.{Fn, Tuple}

fn scaled(values...: List[i32], factor: i32) -> i32: factor  # error: nonfinal-vararg
fn count(values...: i32) -> i32: values                       # error: type-mismatch
fn apply(callback: fn(i32...) -> i32) -> i32: 0               # error: type-mismatch

fn add2(a: i32, b: i32) -> i32: a + b
fn call[Args < Tuple, O, R](f: Fn[Args, O, $ R], args...: Args) -> O $ R: f(args...)
fn nested() -> i32: call(add2, (1, 2))                         # error: type-mismatch

Why. Declaring args... mirrors spreading args.... The type after : is the type the body sees, so a vararg needs no type rule of its own. A List[T] vararg shows in the function's type as a rest element, so a function value keeps its varargs.

See also: Positional Spreads.

#Function Types And Values

Functions are values. A plain function type lists parameter types and a result:

fn(string) -> string
fn!(UserId) -> Result[User, DbError] $ Database
  1. fn.type.valuesFunctions are values.
  2. fn.type.formA plain function type lists parameter types and a result.
  3. fn.type.no-namesParameter names and default values are not part of a function value type.
  4. fn.type.positionalCalling through a function value therefore uses positional arguments only and does not inherit declaration defaults.
  5. fn.type.positional.namedA named argument in a call through a function value names no parameter, so it is an error. Error: unknown-named-argument.
  6. fn.type.partsSuspension and requirement rows are part of the type.

#Function Type Constructors

Every function type is exact sugar for one of two standard constructors that std.function declares:

Sugar Constructor form
fn(A, B) -> O $ R Fn[(A, B), O, $ R]
fn!(A, B) -> O $ R SuspendFn[(A, B), O, $ R]
fn(A) -> O Fn[(A,), O, $()]
fn() -> O $ Db + Cache Fn[(), O, $ Db + Cache]
use std.function.{Fn, SuspendFn}

trait Database

fn check(id: i32, name: string) -> bool: id > 0
fn load!(id: i32) -> string $ Database: "user"

fn spelled() -> Fn[(i32, string), bool, $()]:
    check

fn sugared(callback: Fn[(i32, string), bool, $()]) -> fn(i32, string) -> bool:
    callback

fn suspending() -> SuspendFn[(i32,), string, $ Database]:
    load
  1. fn.type.ctor.declstd.function declares the function type constructors Fn and SuspendFn. Each takes three arguments: the inputs, the output, and the requirement row.
  2. fn.type.ctor.inputsThe inputs argument is one tuple type whose elements are the parameter types, such as (), (A,), or (A, B). It may also be a type parameter bounded by Tuple.
  3. fn.type.ctor.no-flattenA tuple is never flattened into parameters: Fn[((A, B),), O, $ R] takes one pair, and Fn[(A, B), O, $ R] takes two values.
  4. fn.type.ctor.rowThe row argument is row-kinded. A function type without a requirement clause has the empty row $(), and several keys are joined with +, as in $ Db + Cache.
  5. fn.type.ctor.row.dollarThe row argument is a row slot, so one key or a row alias is written after $ too, as in Fn[(), O, $ Db] and Fn[(), O, $ AppRow]. A bare one is an error. Error: generic-kind-mismatch.
  6. fn.type.ctor.tuple-traitstd.function also declares Tuple, an empty sealed marker trait that every tuple type implements, () and (A,) included.
  7. fn.type.ctor.tuple-trait.restA tuple type with a rest element is a tuple type and implements Tuple, so Args may be (i32, List[i32]...).
  8. fn.type.ctor.inputs-tupleAn inputs argument that is not a tuple type or a type parameter bounded by Tuple is an error. Examples are Fn[i32, i32, $()], and Fn[Args, O, $ R] with an unbounded Args. Error: generic-kind-mismatch.
  9. fn.type.ctor.sugarfn(A) -> O $ R and Fn[(A,), O, $ R] denote the same type, and so do fn!(A) -> O $ R and SuspendFn[(A,), O, $ R].
  10. fn.type.ctor.anywhereEither spelling is valid anywhere a type may appear, including implementation targets.
  11. fn.type.ctor.diagnosticsDiagnostics print a function type in its sugar form, as they print T? for Option[T].
  12. fn.type.ctor.importsThe sugar needs no import. The names Fn, SuspendFn, and Tuple are imported where they are written, as in use std.function.Fn.
  13. fn.type.ctor.opaque-sourcesThe constructors have no fields and no construction syntax. Function values come only from function names, closures, one-payload variant constructors, instantiated generic functions, and method references.
Error example
use std.function.{Fn, Tuple}

trait Describe:
    fn describe(self) -> string

impl[Args < Tuple, O, $R] Describe for Fn[Args, O, $ R]:
    fn describe(self) -> string: "function"

fn invalid(callback: Fn[i32, i32, $()]) -> void: pass             # error: generic-kind-mismatch
fn unbounded[Args, O](callback: Fn[Args, O, $()]) -> void: pass  # error: generic-kind-mismatch

#Vararg Inputs

A function with a List[T] vararg has inputs that end in the rest element List[T]..., and its function value keeps the vararg:

fn g(a: usize, b: usize, xs...: List[i32]) -> usize: a + b + xs.len()
fn h(a: usize, b: usize, xs: List[i32]) -> usize: a + b + xs.len()

fn apply(callback: fn(usize, usize, List[i32]...) -> usize) -> usize:
    f := g
    f(1, 2, 3, 4) + callback(1, 2) + h(1, 2, [3, 4])
Function Type Constructor form
g fn(usize, usize, List[i32]...) -> usize Fn[(usize, usize, List[i32]...), usize, $()]
h fn(usize, usize, List[i32]) -> usize Fn[(usize, usize, List[i32]), usize, $()]
  1. fn.type.vararg-restA List[T] vararg is the rest element List[T]... of its function's inputs tuple. So g above has type fn(usize, usize, List[i32]...) -> usize, which differs from h's type.
  2. fn.type.rest-callCalling a function value whose inputs end in a rest element List[T]... treats that element as a List[T] vararg, as in f(1, 2, 3, 4) above.
  3. fn.type.tuple-vararg-inputA vararg whose type is a tuple type or a Tuple-bounded type parameter is one ordinary input of its function's type. That input takes the collected tuple. call has type fn(Fn[Args, O, $ R], Args) -> O $ R.
Error example
fn sum(values...: List[i32]) -> i32: values[0]
fn apply_pair(callback: fn(i32, i32) -> i32) -> i32: callback(1, 2)
fn apply_list(callback: fn(List[i32]) -> i32) -> i32: callback([1, 2])

fn first() -> i32: apply_pair(sum)  # error: type-mismatch
fn second() -> i32: apply_list(sum)  # error: type-mismatch

#No Access Permission

  1. fn.type.no-permissionA function value carries no access permission, and calling one never needs mutable access to it.
  2. fn.type.no-mutmut applied to a function type, as in mut (fn() -> i32) or mut Fn[(), i32, $()], is an error. Error: syntax-error.
  3. fn.type.no-mut.syntaxmut written directly before fn, in a type or a closure header, is an error. Error: syntax-error.
Error example
fn counter() -> mut fn() -> i32:  # error: syntax-error
    fn() -> i32: 1

Why. A function value has no fields to take a permission on. A closure may mutate its captures freely, so the type needs no mutation capability.

#Passing Function Values

  1. fn.type.named-valueA named function value may be passed anywhere its function type is expected.
  2. fn.type.variant-valueSo may a variant constructor with exactly one payload field, such as SyncError.Fs of type fn(FsError) -> SyncError.
  3. fn.type.declared-varianceFunction types follow the declared variance of Fn and SuspendFn: contravariant in each input element, covariant in the output, and invariant in the row.
  4. fn.type.variance-reprLike every variance conversion, a function-type conversion must be representation-preserving.
  5. fn.type.variance-repr.excludedTrait-value construction and optional injection therefore never convert a parameter or result of a function type. Error: variance-representation-change.
Error example
fn count() -> i32: 1

fn erase() -> fn() -> *dyn Display:
    count  # error: variance-representation-change

Why. The polarity rules of Variance already treat parameters as negative and results as positive. Declaring that variance on the constructors removes a special case.

Note. A function value also fits a function type whose row is wider than its own. That is row subsumption, not a variance conversion, and it may adapt the value.

See also: Enum Declarations, Representation-Preserving Variance.

#Generic Function Values

A generic function passed as an argument takes its type arguments from the call:

fn identity[T](value: T) -> T:
    value

fn apply[A, B](value: A, f: fn(A) -> B) -> B:
    f(value)

count := apply(3, identity)
  1. fn.type.generic.not-polymorphicGeneric functions are not first-class polymorphic values.
  2. fn.type.generic.instantiate-everyReferring to one as a value must instantiate every generic parameter.
  3. fn.type.generic.instantiate-fromThe type arguments come from an expected monomorphic function type, from an explicit type-argument list, or from the call the value is an argument of.
  4. fn.type.generic.placeholderA placeholder in that list may be solved from the expected monomorphic type.
  5. fn.type.generic.argumentWhen the value is an argument of a call, its type arguments are solved together with the call's other type variables.
  6. fn.type.generic.argument.sourcesThose variables are solved from the other arguments, the expected result type, and the called function's constraints, as for the call itself.
  7. fn.type.generic.defaultA parameter that those sources leave unsolved takes its default, as at a call. For fn empty[C = List[i32]]() -> C, make := empty has type fn() -> List[i32].
  8. fn.type.generic.unsolvedA generic parameter of the value that remains unsolved is an error. Error: cannot-infer-type.
  9. fn.type.generic.monomorphicThe resulting value has an ordinary monomorphic function type.

In count := apply(3, identity), A is i32 from the first argument, and identity's T and the call's B are solved as i32 with it.

Error example
fn identity[T](value: T) -> T:
    value

fn call_with_nothing[A, B](f: fn(A) -> B) -> void:
    pass

fn main() -> void:
    call_with_nothing(identity)  # error: cannot-infer-type
Open in playground

Why. Only use sites infer. A function declaration's own generic parameters and signature are always written, never inferred from its body or its callers.

See also: Type Inference Boundaries, Variant Constructors As Function Values.

#Suspending Function Values

  1. fn.type.suspend.callA value of type fn!(A) -> T $ R constructs mut Suspend[T] when called normally.
  2. fn.type.suspend.bangIt may be bang-called inside a suspending body.
  3. fn.type.suspend.weakenIt may be weakened to the lowered constructor type fn(A) -> mut Suspend[T] $ R.
  4. fn.type.suspend.no-reverseThe reverse conversion is not implicit.
  5. fn.type.suspend.ctorIn constructor form, the weakening converts SuspendFn[I, O, $ R] to Fn[I, mut Suspend[O], $ R].
fn fetch!(key: string) -> string: "value of $key"

fn store() -> void:
    _ := fetch("k1")   # a mut Suspend[string]; nothing has run

#Method References

A method reference names a method or an associated function as a function value. It is the qualified call without its arguments:

struct Counter:
    value: i32

impl Counter:
    fn bump(&mut self, by: i32) -> void:
        self.value = self.value + by

    fn read(self) -> i32:
        self.value

    fn zero() -> Counter:
        Counter { value: 0 }

fn reader() -> fn(Counter) -> i32:
    Counter::read

fn maker() -> fn() -> Counter:
    Counter::zero

fn bumper(counter: *mut Counter) -> fn(i32) -> void:
    counter::bump
  1. fn.ref.unboundOwner::name without an argument clause, where Owner names a type, a trait, or a type parameter, is an unbound method reference.

  2. fn.ref.unbound.receiverFor a method, the reference takes the receiver first, then the method's own parameters: Counter::bump has type fn(&mut Counter, i32) -> void.

  3. fn.ref.unbound.value-selfA self method gives a Self first parameter, and so does the mut self method of a struct, a list or a map.

  4. fn.ref.unbound.ref-selfA &self method gives a &Self first parameter, and a &mut self method a &mut Self one.

  5. fn.ref.associatedFor an associated function, the parameters are the function's own: Counter::zero has type fn() -> Counter.

  6. fn.ref.lookupA reference resolves name as the qualified call Owner::name(...) does. That is inherent members of a type first, then its available traits, and a type parameter through its bounds.

  7. fn.ref.lookup.ambiguousTwo trait candidates for one name are an error. Error: ambiguous-method.

  8. fn.ref.trait-selfFor Trait::name, Self is inferred from the expected function type, as a generic function value's parameters are.

  9. fn.ref.trait-self.unsolvedA trait reference whose Self nothing determines is an error. Error: cannot-infer-type.

  10. fn.ref.genericA generic member's reference writes its type arguments after the name, as in Json::decode::[User], and the owner's before the member's ::, as in Box::[i32]::get.

  11. fn.ref.generic.instantiateEvery type parameter of the member is instantiated as for a generic function value.

  12. fn.ref.boundvalue::name, where value names a value rather than a type or trait, is a bound method reference.

  13. fn.ref.bound.captureIt evaluates value once, when the reference is created, and captures that receiver. Calling the reference later calls the method on the captured receiver, whatever value names by then.

  14. fn.ref.bound.typeIts parameters are the method's own, without the receiver: counter::bump has type fn(i32) -> void.

  15. fn.ref.bound.addressA bound reference to a &self or &mut self method takes the receiver's address when it is created, as a call does. Calling it later reads or changes that place.

  16. fn.ref.bound.address.writableA bound reference to a &mut self method needs a writable place or a &mut reference as its receiver, as the call does. Error: not-writable.

  17. fn.ref.callA reference followed by an argument clause is an ordinary call, so user::domain() calls domain on user.

  18. fn.ref.suspendingA suspending method is referenced without !, as Store::load, and the reference has a suspending function type such as fn!(Store, Key) -> Blob.

  19. fn.ref.valueA reference is an ordinary function value: it has no parameter names or defaults, and it carries the member's row and suspension.

  20. fn.ref.no-fields:: names only methods and associated functions, never fields. User::email for a field email is an error. Error: unknown-method.

  21. fn.ref.bound.associatedA bound reference names a method, since an associated function has no receiver to bind. counter::zero for an associated function zero is an error. Error: unknown-method.

  22. fn.ref.dynamic.reference-receiverA trait method reference may take a trait value reference as its first parameter and dispatches through its vtable.

  23. fn.ref.dynamic.bound-referenceA bound method reference on a trait value reference captures that reference once and calls the method on its place.

  24. fn.ref.dynamic.permissionA method reference retains the receiver permission a direct call requires. A mutating method needs &mut dyn, and a reference to it through &dyn is an error. Error: not-writable.

Note. The writable-place comparison concerns calls on existing places. Temporary receiver calls are separate from bound references, which follow fn.ref.bound.address.writable.

Error example
struct Counter:
    value: i32

impl Counter:
    fn bump(&mut self, by: i32) -> void:
        self.value = self.value + by

    fn zero() -> Counter:
        Counter { value: 0 }

struct User:
    email: string

fn emails() -> fn(User) -> string:
    User::email  # error: unknown-method

fn frozen(counter: Counter) -> fn(i32) -> void:
    counter::bump  # error: not-writable

fn restart(counter: Counter) -> fn() -> Counter:
    counter::zero  # error: unknown-method

Why. A reference reads as the call it stands for, as in Rust, Java, and Go. A bound reference fixes its receiver when it is made, as Kotlin and Go method values do. A closure such as fn(): counter.read() instead reads the variable when it runs. Fields stay closures, so a field and a method may share a name without a clash.

See also: Associated Function Calls, Trait-Qualified Calls.

#Closures

A closure uses fn without a name:

initial := fn(text: string) -> string:
    text.slice(0, 1)
  1. fn.closure.formA closure uses fn without a name.
  2. fn.closure.no-other-syntaxThere is no separate arrow or shorthand-argument closure syntax.
  3. fn.closure.one-lineA one-line closure uses the same : suite syntax.
inc := fn(x: i32) -> i32: x + 1
  1. fn.closure.monomorphicA closure declares no type parameters and always has a monomorphic function type.
  2. fn.closure.monomorphic.sourcesIts parameter and result types come from its annotations or from the expected function type, as Closure Annotations states.
  3. fn.closure.no-type-parametersA closure header has no place for a type-parameter list, so fn[T](x: T): x is an error. Error: syntax-error.
Error example
fn run() -> i32:
    same := fn[T](value: T) -> T: value  # error: syntax-error
    0

Why. A closure is a value, and generic functions are not first-class polymorphic values (fn.type.generic.not-polymorphic). A method reference is instantiated the same way (fn.ref.generic.instantiate).

#Suspending Closures And Clauses

A suspending closure places ! after fn:

loader := fn!(id: UserId) -> Result[User, DbError] $ Database:
    db := $.use(Database)
    db.load_user!(id)
  1. fn.closure.suspendingA suspending closure places ! after fn.
  2. fn.closure.requirementsA requirement clause follows its result type.
  3. fn.closure.clause-ownerThe clause directly before a header's : always belongs to the function or closure being declared, even when its result is a function type.
  4. fn.closure.clause-groupedA returned function type with its own row is parenthesized, as in fn make() -> (fn() -> i32 $ Log) $ Console:.

See also: Types.

#Closure Annotations

When an expected function type is available, an inline closure may omit parameter and result annotations:

fn apply_all(values: List[i32], transform: fn(i32) -> i32) -> List[i32]:
    [for value in values => transform(value)]

doubled := apply_all(counts, fn(count): count * 2)
  1. fn.closure.annotations-omittedWhen an expected function type is available, an inline closure may omit parameter and result annotations.
  2. fn.closure.needs-annotationWithout a sufficient expected type, parameters must be annotated. An unannotated parameter is then an error. Error: closure-parameter-needs-annotation.
  3. fn.closure.result-inferredA nonrecursive closure may infer its result type from its body.
  4. fn.closure.result-inferred.commonThe inferred result is the least common type of the body's final value and every return operand. When they have no common type, the closure is an error. Error: no-common-type.
  5. fn.closure.result-expectedAn expected function type may instead supply the result type.
  6. fn.closure.recursive-resultA recursive local closure must always write its result type explicitly, even if an expected function type could supply it. Error: recursive-function-needs-result-type.
Error example
fn run() -> i32:
    closure := fn(value) -> i32: value  # error: closure-parameter-needs-annotation
    0

#Captures

This section defines what a closure captures and how it may use its captures.

  1. fn.capture.localsA closure captures local bindings that it references from enclosing lexical scopes.
  2. fn.capture.no-providersA closure captures no provider from an enclosing $.with scope: each requirement key its body uses goes into its row (req.row.omitted.outer-scope).
  3. fn.capture.provider-valueA provider value bound to a local by $.use is captured like any other local, and stays bound after its provider scope ends.
trait Clock:
    fn now(self) -> i32

fn make_reader() -> fn() -> i32 $ Clock:
    let clock: *dyn Clock = $.use(Clock)
    fn() -> i32: clock.now()

See also: Lexical And Dynamic Providers.

#Copied And Shared Captures

A closure copies the immutable bindings it captures and shares the variables it captures:

struct Point:
    x: i32
    y: i32

fn demo() -> i32:
    fixed := Point { x: 1, y: 2 }
    let mut moving = Point { x: 1, y: 2 }
    read_fixed := fn() -> i32: fixed.x
    shift := fn() -> void: moving.x = moving.x + 10
    shift()
    read_fixed() + moving.x
  1. fn.capture.copyA captured immutable binding is copied into the closure when the closure is created. The immutable bindings are := and using bindings and the parameters that are not mut.
  2. fn.capture.copy.valueThe copy is the one copy := binding makes: a struct or a collection becomes the closure's own value, and a reference stays a reference to the same place.
  3. fn.capture.copy.shadowA binding in an inner scope that reuses the captured name is a new binding. The closure keeps the one it captured.
  4. fn.capture.cellA captured variable is shared with its scope through a cell. The variables are let and let mut bindings, mut parameters, and the bindings of a for, match, or let pattern.
  5. fn.capture.cell.writesEach side sees the other's writes: assignments, in-place field writes, and collection changes such as p.x = 1 and xs.push(1).
  6. fn.capture.cell.freshEach run of a variable's declaration makes a new cell. A closure shares the cell that existed when the closure was created.
  7. fn.capture.cell.closuresClosures that capture one variable share its cell with each other.
  8. fn.capture.copiesCopying a closure value copies no cell: the copies share every cell the closure captured.
  9. fn.capture.call.anyA closure may be called any number of times, through any copy of it.
  10. fn.capture.referenceCapturing a counted reference copies the reference, as ref.capture.copy states.
  11. fn.capture.no-local-referenceA closure must not capture a local reference, &T or &mut T, as ref.local.escape.capture states. Error: local-reference-escapes.

Why. An immutable binding never changes, so copying it and sharing it look the same. A variable changes, so the closure and its scope must refer to one place, as Go, Swift, and Kotlin closures do.

See also: Loops.

#Plain Closures

Every closure is a plain fn closure, and it may mutate through its captures:

struct User:
    name: string

fn rename(user: &mut User) -> void:
    user.name = "renamed"

fn plain_closure(user: *mut User) -> fn() -> void:
    fn() -> void:
        rename(user)

fn make_appender(items: *mut List[i32]) -> fn(i32) -> void:
    fn(value: i32) -> void:
        items.push(value)
  1. fn.capture.accessA closure uses each capture with the access that the captured binding has in the enclosing scope.
  2. fn.capture.mutateA closure may assign captured let storage, change a captured let mut binding in place, and write through a captured *mut T reference.
  3. fn.capture.mutate.formsCalling a &mut self method on a captured let mut list or through a captured *mut T reference is such a use. So are passing a captured *mut T reference to a &mut T parameter, and returning it.
  4. fn.capture.readonlyA read-only capture stays read-only. Assigning a field through a captured *T reference is an error. Error: not-writable.
  5. fn.capture.result-not-weakenedA callable's declared *mut T result is not weakened when the callable is read through a read-only reference.
Error example
struct User:
    name: string

fn rename(user: *User) -> void:
    change := fn() -> void:
        user.name = "Grace"  # error: not-writable
    change()

#Mutable Closures

A closure that mutates captured state is an ordinary closure with an ordinary function type:

let count = +0

let next: fn() -> i32 = fn() -> i32:
    count = count + 1
    count
  1. fn.capture.no-mut-formThere is no mutable closure type or literal. A closure that mutates captured state has an ordinary fn(...) -> T type.
  2. fn.capture.storage.lifetimeIf a closure outlives the original stack activation, the runtime preserves its captured storage through garbage collection.

Why. hd has no unique borrows, so a mutation capability on function types would protect nothing. Swift, Kotlin, and Go closures also mutate their captures freely.

#Scope Of This Section

  1. fn.capture.in-processThis section defines ordinary in-process closure behavior only.
  2. fn.capture.serializable-deferredSerializable closure capture, how a restored closure names its code beyond the same compiled program, and restoration semantics are deferred to the runtime design.
fn apply_twice(f: fn(i32) -> i32, x: i32) -> i32:
    f(f(x))

fn demo() -> i32:
    apply_twice(fn(n) -> i32: n + 1, 10)

#Multiple Inline Closures

Multiple multiline closures may be passed by parenthesizing each closure expression and separating the arguments with commas:

choice(
    (
        fn(a):
            println(a)
    ),
    (
        fn(b):
            println(b)
    ),
)
  1. fn.multi.parenthesizedMultiple multiline closures may be passed by parenthesizing each closure expression and separating the arguments with commas.
  2. fn.multi.commasNewlines do not replace commas in argument lists.
  3. fn.multi.unparenthesizedWithout parentheses, the line after an indented closure body must start with , or a closing delimiter. It must be indented no farther than the line holding the closure header.
  4. fn.multi.next-closureThe next closure may start on that line, as in , fn(b):.
  5. fn.multi.syntax-errorA later argument written at body indentation, or a closing delimiter at the end of a body line, is a syntax-error.

Why. Parentheses make each multiline closure's boundary explicit.

See also: Physical And Logical Lines.

#Trailing Callback Blocks

When the final parameter has a zero-argument function type, a call may supply it as an indented trailing block:

result := when(a, b):
    compute_result()

transaction:
    save_user()
  1. fn.trailing.formWhen the final parameter has a zero-argument function type, a call may supply it as an indented trailing block.
  2. fn.trailing.argumentsOrdinary arguments remain in parentheses.
  3. fn.trailing.empty-parenthesesIf there are no ordinary arguments, empty () is omitted.
  4. fn.trailing.closureThe trailing block is equivalent to a zero-argument closure whose result and behavior are contextually inferred from the final parameter.
  5. fn.trailing.positionA trailing block call may be a complete statement or the complete right-hand side of :=, let ... =, =, _ :=, return, or break. Examples are total = sum_of(items): and return retry(3):, each followed by the block.
  6. fn.trailing.oneOnly one trailing block is permitted, and only for a zero-argument final parameter. Error: trailing-block-position.
  7. fn.trailing.parameterizedParameterized callbacks use explicit closure syntax.
  8. fn.trailing.returnreturn inside the block returns from the generated callback, not from the enclosing function.
  9. fn.trailing.suspendingWhen the final parameter has a suspending function type, as in body: fn!() -> T, the trailing block is a suspending closure. This holds for every callee.
  10. fn.trailing.suspending.bodySuch a block is a suspending body, so it may make bang calls.
Error example
transaction:
    save_user()
:  # error: trailing-block-position
    save_again()

A trailing block for an fn! parameter may make bang calls:

fn fetch_count!() -> i32: 3

fn twice!(body: fn!() -> i32) -> i32:
    body!() + body!()

fn total!() -> i32:
    count := twice!():
        fetch_count!()
    count

Why. The ! in the callee's parameter type marks the block as suspending, as a Kotlin suspend function-type parameter does for its trailing lambda.

#Generic Functions

Generic parameters follow the function name:

fn first[T](items: List[T]) -> T?:
    ...

Trait bounds compose with &:

fn audit[T < Display & Named](value: T) -> string:
    ...

Callers may rely on inference or write an explicit type argument list:

first(names)
first::[string](names)
  1. fn.generic.parametersGeneric parameters follow the function name.
  2. fn.generic.bounds-andTrait bounds compose with &.
  3. fn.generic.call-listCallers may rely on inference or write an explicit type argument list, which may omit trailing slots.
  4. fn.generic.call-markerIn an expression, the explicit list follows ::, as in first::[string](names), while a type keeps List[string].
  5. fn.generic.erasedGeneric parameters are erased.

#Explicit Type Arguments

  1. fn.generic.explicit.trailingAn explicit type-argument list may omit trailing slots. Each omitted slot is inferred as a _ slot is, then takes its default if inference leaves it unsolved.
  2. fn.generic.explicit.too-long-countA list with more slots than the function has generic parameters is an error. Error: argument-count.
  3. fn.generic.explicit.row-dollarThe slot of a row parameter takes a row type argument, written after $ even for one key or a row alias. Examples are ::[$ Log], ::[$ AppRow], and ::[$()]. A bare key or row alias there is an error. Error: generic-kind-mismatch.
fn pair[Left, Right](left: Left, right: Right) -> (Left, Right):
    (left, right)

value := pair::[string]("left", 1)  # Right is inferred as usize, the fallback
Error example
fn pair[Left, Right](left: Left, right: Right) -> (Left, Right):
    (left, right)

fn triple() -> (string, i32):
    pair::[string, i32, bool]("left", 1)  # error: argument-count

Why. A list names only the leading arguments the reader should see or inference cannot find, as C++ deduces trailing template arguments. The rest are inferred, or take their defaults.

An explicit list may write _ in any slot to infer that argument:

fn convert[From, To](value: From) -> To:
    ...

user := convert::[_, User](payload)
  1. fn.generic.placeholderAn explicit list may write _ in any slot to infer that argument.
  2. fn.generic.placeholder.solveA placeholder is solved from call arguments, the expected result type, and the function's generic constraints.
  3. fn.generic.placeholder.defaultWhen those constraints do not determine one type, the parameter's default applies.
  4. fn.generic.placeholder.unsolvedA placeholder that neither the constraints nor a default determine is an error. Error: cannot-infer-type.
  5. fn.generic.placeholder.not-type_ is a call-site inference instruction, not a type, and cannot appear in an ordinary type argument list such as List[_].
Error example
fn make[T]() -> T:
    panic("not implemented")

value := make::[_]()  # error: cannot-infer-type

#Generic Methods And Qualified Calls

The same explicit-list rules apply to generic methods:

parser.parse::[User](text)
parser.convert::[_, User](payload)
  1. fn.generic.methodsThe same explicit-list rules apply to generic methods.
  2. fn.generic.qualifiedThey also apply to qualified calls of generic associated functions and trait methods.
  3. fn.generic.qualified.member-listThe method-level list follows the member name, as in Type::name::[T](...) and Trait::name::[T](receiver, ...).
  4. fn.generic.qualified.owner-listType arguments of the qualifying type or trait stay before the member's ::, as in Add::[Money]::add(left, right).
  5. fn.generic.bangA bang call keeps the ! on the name, as the declaration does, and writes the list after it.
  6. fn.generic.bang.examplesfn fetch![T](...) is called as fetch!::[User](key), and suspending methods as parser.load!::[User](text) and Store::load!::[User](key).
  7. fn.generic.method-inferenceA generic method may still rely entirely on inference by omitting the list.
  8. fn.generic.dot-member-valueA dot member with type arguments and no call, as in parser.parse::[User], is not a function value; the reference form is parser::parse::[User].

#Methods And Receivers

Functions declared in impl blocks are methods when their first parameter is a receiver: self, mut self, &self, &mut self, *self, or *mut self:

impl User:
    fn domain(self) -> string:
        self.email.slice(1, self.email.len())

    fn tagged[T](self, value: T) -> T:
        value

label := user.tagged::[string]("admin")
  1. fn.method.receiver-formsA function declared in an impl block is a method when its first parameter is a receiver, in one of the forms of the table below.
  2. fn.method.no-long-formA receiver has no long form, so self: &mut Self is an error. Error: syntax-error.
  3. fn.method.mut-self-structOn a struct, a list or a map, mut self is the method's own changeable copy of the receiver, a mut parameter named self. The caller's value is unchanged unless the method returns the copy and the caller keeps it.
  4. fn.method.no-ownership-receiverThere is no ownership-taking receiver form.
  5. fn.method.associatedReceiverless members are associated functions and are called with qualified Type::function(...) or Trait::function(...) syntax.
  6. fn.method.eval-orderMethod-call syntax evaluates the receiver first and then ordinary arguments.
  7. fn.method.equivalenceIt is semantically equivalent to selecting the resolved method and supplying the receiver as its first argument.
  8. fn.method.value-deferredThe treatment of bare receiver.method as a function value is deferred.
Receiver Type of self What the method gets
fn.method.receiver.selfself Self the method's own copy of the receiver, which never changes
fn.method.receiver.mut-selfmut self Self on a struct, a list or a map, the method's own changeable copy of the receiver
fn.method.receiver.ref-self&self &Self a read of the caller's value, with no copy
fn.method.receiver.ref-mut-self&mut self &mut Self the caller's value, changed in place
fn.method.receiver.counted-self*self *Self a read of the caller's value, which the method may keep
fn.method.receiver.counted-mut-self*mut self *mut Self the caller's value, changed in place, which the method may keep

#Reference Receivers

A &self, &mut self, *self, or *mut self method refers to the caller's value, and a call takes the receiver's address for it:

struct Vec2:
    x: f64
    y: f64

impl Vec2:
    fn len_squared(&self) -> f64:
        self.x * self.x + self.y * self.y

    fn shift(&mut self, d: Vec2) -> void:
        self.x += d.x
        self.y += d.y

fn demo() -> f64:
    let mut p = Vec2 { x: 1.0, y: 2.0 }
    p.shift(Vec2 { x: 1.0, y: 1.0 })   # (&mut p).shift(..): p is now (2, 3)
    p.len_squared()                      # 13.0
  1. fn.method.ref-selfIn a &self method self is a reference &Self, and in a &mut self method &mut Self. Reads and writes through it follow Values And References.
  2. fn.method.ref-self.kindsA method of any type may take &self or &mut self: a struct, an enum, or a primitive.
  3. fn.method.ref-self.local&self and &mut self are local references, so the method must not store, return, or capture self. Error: local-reference-escapes.
  4. fn.method.ref-self.local.fixThe diagnostic's fix-it writes *self for &self, and *mut self for &mut self.
  5. fn.method.counted-selfIn a *self method self is a counted reference *Self, and in a *mut self method *mut Self. The method may store, return, and capture it.
  6. fn.method.counted-self.kindsA method of any type may take *self or *mut self, as for &self.
  7. fn.method.counted-self.addressA *self or *mut self call takes its receiver's address by the rules below for &self and &mut self calls, and the reference it takes is counted.
  8. fn.method.counted-self.local-receiverA *self or *mut self call through a local reference is an error, since a local reference never converts to a counted one. Error: local-reference-escapes.
  9. fn.method.auto-addressCalling a &self or &mut self method on a place takes the place's address. p.shift(d) means (&mut p).shift(d), and no mark appears at the call.
  10. fn.method.auto-address.placeA place is what & refers to (Places And New Objects): a binding, a struct field of a place, or a value reached through a reference.
  11. fn.method.auto-address.referenceA receiver that is itself a reference is passed on, so through r: &mut Vec2, r.shift(d) changes what r refers to.
  12. fn.method.auto-address.temporaryOn a non-place temporary, a &self or &mut self call refers to a new object holding its value.
  13. fn.method.auto-address.temporary.callThe receiver temporary is kept alive until the call returns.
  14. fn.method.auto-address.place-writableWhen a &mut self call addresses a place, that place must be writable. Error: not-writable.
  15. fn.method.auto-address.fixThe fix-it writes let mut for a let or := binding, mut before a parameter name, and &mut self for a self receiver.
  16. fn.method.auto-address.shared-referenceA &mut self call through a &T reference is an error. Error: not-writable.
  17. fn.method.auto-address.collection-elementA list element or a map value is a place for the call, as in bodies[i].shift(d) and teams[name].rename(n), also through a reference to the collection, as in r[i].shift(d). The call takes an element reference (Element References) whose access lasts from before the arguments are evaluated until the call returns. A &mut self call needs the collection in a writable place, as &mut xs[i] does.
  18. fn.method.self-struct-writeWriting a field of self in a struct's self method is an error, with a fix-it that takes &mut self. Error: non-reassignable-parameter-binding.
Error example
struct Point:
    x: i32
    y: i32

impl Point:
    fn shift(&mut self, dx: i32) -> void:
        self.x += dx

    fn reset(self) -> void:
        self.x = 0  # error: non-reassignable-parameter-binding

fn make() -> Point:
    Point { x: 0, y: 0 }

fn demo() -> void:
    origin := Point { x: 0, y: 0 }
    origin.shift(1)  # error: not-writable
    let fixed = Point { x: 1, y: 1 }
    fixed.shift(1)  # error: not-writable
    make().shift(1)  # a writable temporary, discarded after this call

Why. A method says whether it changes its receiver, and the let mut binding says that the value may change, so the call needs no mark, as in Go. A temporary is a fresh writable value, so a chain needs no intermediate binding. A self method that writes a field would change a copy nobody sees, Go's lost write through a value receiver, so it does not compile.

See also: Member Resolution, which defines member lookup, including promotion; Traits, which defines dynamic dispatch; Member Access.

#Recursion

This section defines recursive functions and closures.

  1. fn.recursion.named-cycleNamed functions may call themselves or other visible named functions recursively, provided at least one function in each recursive cycle has a declared result type. The others may omit theirs.
  2. fn.recursion.closure.no-self-nameClosures do not acquire an implicit self-name.
  3. fn.recursion.closure.bindingA closure directly initialized by a statement-form local := or let binding may refer to that binding's name inside its body.
  4. fn.recursion.closure.resultIts result type after -> is mandatory.
  5. fn.recursion.closure.parametersParameter types may be supplied by an expected function type or written on the closure.
  6. fn.recursion.closure.checkThe compiler checks recursive calls against that established function type, not against a result inferred from the recursive body.
  7. fn.recursion.closure.outerOrdinary references in the initializer outside the closure body still resolve in the outer scope.
  8. fn.recursion.closure.ordinary-bindingThe self-reference uses the ordinary binding.
  9. fn.recursion.closure.letWith let, reassignment changes which function a later recursive call invokes.
  10. fn.recursion.closure.initializedA closure body cannot execute until its binding's initializer completes.
  11. fn.recursion.closure.no-forwardThis exception does not enable general forward references or mutual recursion between local closures.
Error example
fn sum_to(limit: i32) -> i32:
    sum := fn(n: i32):  # error: recursive-function-needs-result-type
        if n == 0: 0
        else: n + sum(n - 1)
    sum(limit)

See also: Declarations.

#Program Entry Functions

This section defines program entry functions.

  1. fn.entry.mainpub fn main() -> void or pub fn main() -> Result[void, E] is the default non-suspending entry point.
  2. fn.entry.no-parametersIt has no source-level parameters.
  3. fn.entry.suspendingA suspending entry point is named main!.
  4. fn.entry.requirementsEntry points may declare host requirements with the ordinary $ clause.
  5. fn.entry.pubpub controls module visibility and does not itself create a Wasm host export.
pub fn main() -> void:
    pass
Open in playground

#Unsupported Function Extensions

  1. fn.unsupported.featureshd-lang has no general recursive local binding facility, shorthand-argument closures, or non-local returns from closures.
fn double_all(items: List[i32]) -> List[i32]:
    items.map(fn(x: i32) -> i32: x * 2)   # the closure is written out

#Method Values

Method values are written as :: method references. The rules below still hold:

  1. fn.unsupported.qualified-callA qualified call such as User::guest(), Display::to_string(value), or Add::[Money]::add(left, right) remains an ordinary call.
  2. fn.unsupported.closure-adapterAn explicit closure, such as fn(user: User) -> string: user.domain(), adapts a method where a function value is needed.
  3. fn.unsupported.variant-valueA variant constructor with exactly one payload field, written Enum.Variant with a . and no argument clause, is already a function value.
  4. fn.unsupported.variant-multiA constructor with two or more payload fields stays an error. Error: unsaturated-enum-constructor.
struct Counter:
    value: i32

impl Counter:
    fn read(self) -> i32: self.value

fn demo(counter: Counter) -> i32:
    read := counter::read   # a bound method value: the receiver is fixed now
    read()

See also: Enum Declarations.