Function types

A function is also a value. Its type, a function type, records the parameter types and the result type and nothing else, so any function of the same shape fits wherever that type is expected. A function value can be passed as an argument, held in a local or a field, compared, and called.

function-type = "func" "(" [ parameter-type { "," parameter-type } ] ")" [ "->" result ]
parameter-type = [ name ":" ] type

The type of a function

A function's type is its declaration with the name, the parameter names and the body taken out:

FunctionIts type
func Double(value: int32) -> int32func(int32) -> int32
func Ascending(a: int32, b: int32) -> boolfunc(int32, int32) -> bool
func Report(label: char8[..])func(char8[..])
func Ready() -> boolfunc() -> bool

A function type without -> describes a function with no result. Parameter names may be written inside a function type for readability, func(a: int32, b: int32) -> bool, but they are ignored: only the types count.

Two function types are the same when their parameter types and result types are identical, in order. Nothing converts — a func(int) -> int does not fit a func(int32) -> int32, and a function returning bool does not fit one returning int32.

Function values

A function's name, written without parentheses, is a value of its function type. Writing parentheses calls the function instead.

type Comparator = func(int32, int32) -> bool;

struct Sorter {
    name: char8[..];
    before: Comparator;
}

func Ascending(a: int32, b: int32) -> bool {
    return a < b;
}

func Descending(a: int32, b: int32) -> bool {
    return a > b;
}

func Sort(items: var int32[..], before: func(int32, int32) -> bool) {
    for i in 1..items.length {
        var j = i;
        while j > 0 && before(items[j], items[j - 1]) {
            let held = items[j];
            items[j] = items[j - 1];
            items[j - 1] = held;
            j -= 1;
        }
    }
}

func Main() -> int {
    var numbers: int32[5] = [3, 1, 4, 1, 5];
    Sort(numbers[..], Ascending);            // 1 1 3 4 5

    let sorter = Sorter { name: "down", before: Descending };
    Sort(numbers[..], sorter.before);        // 5 4 3 1 1

    var compare: func(int32, int32) -> bool = Ascending;
    PrintLine("{}", compare(1, 2));          // true
    compare = Descending;
    PrintLine("{}", compare(1, 2));          // false
    PrintLine("{}", compare == Descending);  // true
    return 0;
}
WhereWrittenCalled as
A parameterbefore: func(int32, int32) -> boolbefore(x, y)
A localvar compare: func(int32, int32) -> boolcompare(1, 2)
A fieldbefore: Comparator;sorter.before(x, y)

A local, parameter or field of function type is called exactly like a function, and the call runs whichever function the value holds at that moment. A var local or field can be reassigned to another function of the same type. == and != compare two function values by identity: they are equal when they hold the same function.

A type alias gives a long function type a name. The alias is the same type, so Comparator and func(int32, int32) -> bool are interchangeable, as Sort and Sorter show.

What a function value is not

A function value refers to one declared function and carries no state of its own.

  • There are no closures. There is no lambda or anonymous function expression, and a function value cannot capture a local variable. Data a callback needs is passed to it as an argument, or kept in a struct beside the function field.
  • A generic function is not a value. Its name has no single type until its type parameters are known, so let pick: func(int, int) -> int = Larger; is refused with cannot assign 'func(T, T) -> T' to 'func(int, int) -> int'. Pass a non-generic function that calls it.
  • A method is not a value. card.Area without parentheses is read as a field and fails with has no field 'Area'.
Functions inside a block.
rux 0.4.0 does not yet reject a func declared inside a function body, and a program that calls one builds but cannot start. Declare functions at the top level, in a module, or in an extend block.
Overloaded names.
rux 0.4.0 does not yet choose an overload by the expected function type: the name of an overloaded function is taken as one particular overload, and assigning it to a variable of another overload's type fails with cannot assign. Pass functions that are not overloaded.

Rules and errors

MistakeError
A function of another shape, Sort(numbers[..], IsEven)argument 2 to 'Sort' has type 'func(int32) -> bool8', but parameter 'before' requires 'func(int32, int32) -> bool8'
Calling instead of passing, Sort(numbers[..], Ascending(1, 2))argument 2 to 'Sort' has type 'bool8', but parameter 'before' requires 'func(int32, int32) -> bool8'
Calling a function value with the wrong arguments, compare(1)call to 'compare' expects 2 arguments, but 1 was provided

Error messages print bool by its full name, bool8.

See also