Function Overloading

Multiple functions or methods with one name and compile-time overload resolution.

Several functions can share a name when calls can be resolved by argument count, argument types, and call context.

fn score(value: i32) -> i32 {
    return value + 1;
}

fn score(a: i32, b: i32) -> i32 {
    return a + b;
}

fn score(value: str) -> usize {
    return value.size;
}

fn main() {
    assert score(41) == 42;
    assert score(20, 22) == 42;
    assert score("zynx") == 4;
}

Overload resolution is compile-time. A call that matches no candidate, or matches multiple candidates equally well, is rejected.

Methods And Constructors

Methods can be overloaded too. Constructor calls use overloaded init methods. An init method can return void or void throws(ErrorSet); a throwing initializer makes the constructor call fallible.

struct Counter {
    value: i32,

    init(self) {
        self.value = 0;
    }

    init(self, value: i32) {
        self.value = value;
    }

    fn add(self, value: i32) {
        self.value += value;
    }

    fn add(self, a: i32, b: i32) {
        self.value += a + b;
    }
}

fn main() {
    let first = Counter();
    let second = Counter(10);

    first.add(10);
    first.add(10, 22);

    assert first.value == 42;
    assert second.value == 10;
}

Generic methods, interface methods, imported module functions, and operator methods all participate in the same overload system.

Named Arguments

Named arguments are checked against candidate parameter names, but they do not reorder calls. Once a named argument is used, later arguments must keep moving forward through the parameter list.

fn connect(host: str, port: u16, secure: bool = false) -> u16 {
    return secure ? port + 1 : port;
}

fn main() {
    assert connect("localhost", port: 8080) == 8080;
    assert connect("localhost", port: 443, secure: true) == 444;
}

These calls are rejected:

connect(port: 8080, host: "localhost"); // named arguments must follow order
connect("localhost", host: "other");    // duplicate/reused parameter
connect("localhost", tls: true);        // unknown parameter

Generic Candidates

Generic candidates are considered when their generic arguments can be inferred or supplied explicitly, and their bounds are satisfied.

fn same<T>(value: T) -> T {
    return value;
}

fn same(value: i32, fallback: i32) -> i32 {
    return value == 0 ? fallback : value;
}

fn main() {
    assert same("ok") == "ok";
    assert same(0, 42) == 42;
}

Interface bounds are part of candidate validity. For example, a candidate using T: Integer only matches primitive integer types, not bool or structs. See Generics and Interfaces.

Async Context

Sync and async functions can share a signature. Plain calls prefer the sync candidate; await and an expected future.Future<T> context can select the async candidate.

import std.future;

fn choose(value: i32) -> i32 {
    return value + 1;
}

async fn choose(value: i32) -> i32 {
    _ = await future.yield();
    return value + 2;
}

async fn main() {
    let sync_value = choose(10);
    let async_value = await choose(10);
    let handle: future.Future<i32> = choose(20);
    let handled_value = await handle;

    assert sync_value == 11;
    assert async_value == 12;
    assert handled_value == 22;
}

Resolution Rules

  • Candidate names come from visible module functions, methods, interface methods, constructors, and operator methods.
  • Required and default parameters determine which arities can match.
  • Positional arguments bind first. Named arguments must then follow parameter order without duplicates.
  • Generic arguments are inferred from argument types when possible; explicit generic arguments can be supplied with <...>.
  • Exact matches are preferred over numeric literal conversions and borrowed view conversions such as String to str or str to bytes.Bytes.
  • If two best candidates compare equally well, the call is ambiguous.

See Operators for operator method syntax.