78 Function Overload Resolution

Function overloading provides ad-hoc polymorphism: several ordinary or generic functions may have the same name, including in the same namespace. The Name Resolution chapter specifies how ordinary lookup, selected imports, and argument-dependent lookup assemble the candidate set. This chapter selects a function from that set using the static argument types.

Viable candidates

An ordinary function is viable when the number of arguments matches its ordinary parameter list, every argument has cardinality one after cardinality-flow refinement, and every argument type is assignable to the corresponding parameter type.

Parameter access is checked as part of viability. A plain argument is viable for either a read-only or an owned parameter. An explicit ^s argument is viable only for an owned parameter and requires s to be an initialised variable owned by the caller. Function parameters cannot use the exclusive-reference marker &. Access and ownership markers do not participate in parameter-list specificity and do not distinguish overload declarations whose parameter types are otherwise identical.

A generic function is first instantiated provisionally. Explicit generic arguments are bound first; omitted arguments are inferred from the ordinary argument types and then trailing defaults are applied. The resulting arguments must satisfy their declared kinds and the function's generic where constraints. Substitution must produce a valid concrete signature, and that signature must satisfy the ordinary viability rules. Failure of inference or a false constraint removes that candidate; it does not by itself make the call ill-formed.

More specific parameter lists

Given two viable candidates with instantiated parameter types P1, ..., Pn and Q1, ..., Qn, the first parameter list is more specific than the second when every Pi is a subtype of or equal to Qi, and at least one Pi is a proper subtype of Qi. Specificity uses the subtype relation itself, not the number of edges in an embedding path. It therefore remains well-defined when the type graph contains more than one path between two types.


func String describe(Shape x) = "shape";
func String describe(Rectangle x) = "rectangle";

describe(rectangle)  // selects describe(Rectangle)

If two viable candidates have identical instantiated parameter types, a non-generic declaration is preferred to a generic declaration. No other preference is given merely because a declaration is generic or non-generic, was found by ordinary lookup or ADL, appears earlier in a source file, or comes from the current component. Explicit and inferred generic arguments participate identically after substitution.

Selecting the overload

A viable candidate is better than another viable candidate when its instantiated parameter list is more specific, or when the lists are identical and it is non-generic while the other is generic. The selected overload is the unique viable candidate that is better than every other viable candidate. If there is no viable candidate, the call is invalid. If there is no unique best candidate, the call is ambiguous and must be qualified, supplied with explicit generic arguments, or otherwise rewritten to make the intended overload unique.

Return types do not participate in overload resolution. The expected type of the surrounding expression therefore cannot select between overloads. This keeps call resolution local and avoids changes in a distant result context silently selecting a different function.

Subtyping and incomparable overloads

Subtyping permits an argument to make several overloads viable. A strictly narrower parameter type wins when it is narrower in every parameter position. If candidates trade specificity between positions, neither is better and the call is ambiguous.


func Result combine(Rectangle a, Shape b) = ...;
func Result combine(Shape a, Rectangle b) = ...;

combine(r1, r2)  // ambiguous: each overload is narrower in one position

The same rules apply to identifier-named functions, receiver-call syntax, and symbolic functions. For a receiver call, the receiver is the first ordinary argument before viability and specificity are tested. For an operator, its operands are the ordinary arguments.