Functions · Lesson 4.4

Overload

Source
Give several functions one name and let each call's arguments choose between them.
You'll need: Function, Return, Character

Several functions may share one name, as long as their parameters differ. At each call the compiler looks at the arguments and picks the version that fits, so the caller writes one name and gets the right behaviour for whatever it passes. A set of functions sharing a name is an overload set.

You have been using one since the first lesson. PrintLine is not a single function but over twenty: one for int, one for float64, one for bool, one for a single character, one taking nothing at all, one taking a format string and values, and more. Writing PrintLine(x) has been choosing among them all along.

Told apart by type

Four functions, one name, each taking a different type:

func Describe(value: int) {
    PrintLine("an integer: {}", value);
}

func Describe(value: float64) {
    PrintLine("a number with a fraction: {}", value);
}

…and two more for bool and char. Nothing at the call says which one to run — each argument's type decides, which is why the same spelling produces four different messages:

Describe(42);
Describe(2.5);
Describe(true);
Describe('R');

Told apart by count

Overloads may also differ in how many parameters they take:

func Area(side: int) -> int {
    return side * side;
}

func Area(width: int, height: int) -> int {
    return width * height;
}

func Area(width: float64, height: float64) -> float64 {
    return width * height;
}

Area(4) can only mean the square, Area(4, 5) the integer rectangle and Area(1.5, 2.0) the floating-point one. No call can match more than one of them, so the set is unambiguous.

How a call is resolved

flowchart LR
    call["Area(4, 5)"] --> all["Every function<br/>named Area"]
    all --> fit{"Which take this many<br/>arguments, of these types?"}
    fit -- "exactly one" --> run["That one is called"]
    fit -- "none" --> err["error: no matching overload<br/>for 'Area' with argument types …"]

The argument types have to fit the parameters as they are. Area(4, 2.0) matches nothing: 4 is an int and 2.0 a float64, and there is no Area(int, float64). Write Area(4.0, 2.0) to reach the floating-point version.

Never by the result

Overloading is decided by the parameters, never by the result type. A call does not say what type it wants back, so two functions that differ only in what they return could not be told apart — and the second is refused:

func Half(value: int) -> int { ... }
func Half(value: int) -> float64 { ... }

When the difference is what comes back, give the functions separate names, such as Half and HalfExact.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// Several functions may share one name as long as their parameters differ. The
// compiler picks one by the arguments at each call, so the caller writes one
// name and gets the version that fits.
//
// This is not new: `PrintLine` has been an overload set since the first lesson.
// There is one for `int`, one for `float64`, one for `bool`, one for a single
// character, one taking nothing at all, one taking a format string and values,
// and more — over twenty in total. Writing `PrintLine(x)` has been choosing
// among them all along.
import Io::PrintLine;

// Four functions, one name, told apart by the type of their argument.
func Describe(value: int) {
    PrintLine("an integer: {}", value);
}

func Describe(value: float64) {
    PrintLine("a number with a fraction: {}", value);
}

func Describe(value: bool) {
    PrintLine("a truth value: {}", value);
}

func Describe(value: char) {
    PrintLine("a character: {}", value);
}

// Overloads may also differ in how many parameters they take. These are
// unambiguous because no call can match more than one.
func Area(side: int) -> int {
    return side * side;
}

func Area(width: int, height: int) -> int {
    return width * height;
}

func Area(width: float64, height: float64) -> float64 {
    return width * height;
}

func Main() -> int {
    // Nothing here says which `Describe` to run. Each argument's type decides,
    // which is why the same call spelling produces four different messages.
    Describe(42);
    Describe(2.5);
    Describe(true);
    Describe('R');

    // The count of arguments chooses just as well as their types.
    PrintLine("square           {}", Area(4));
    PrintLine("rectangle        {}", Area(4, 5));
    PrintLine("float rectangle  {}", Area(1.5, 2.0));

    // Overloading is resolved by the parameters, never by the result. Two
    // functions differing only in what they return could not be told apart
    // at a call, so the second one is refused:
    //
    //     func Half(value: int) -> int { ... }
    //     func Half(value: int) -> float64 { ... }
    //         error: function 'Half' has the same parameter signature as an
    //                earlier overload
    //
    // Give them separate names when the difference is what comes back.
    return 0;
}

Run it

cd Examples/Functions/Overload
rux run
an integer: 42
a number with a fraction: 2.5
a truth value: true
a character: R
square           16
rectangle        20
float rectangle  3.0

Common mistakes

Overloads that differ only in their result.
The second Half above fails with error: function 'Half' has the same parameter signature as an earlier overload. Change the parameters, or use two names.
Mixing an integer and a float in one call.
Area(4, 2.0) fails with error: no matching overload for 'Area' with argument types (int, float64). A literal is not converted to suit an overload — write 4.0, or add the overload you actually need.

Try it yourself

  1. Add Describe(value: char8[..]) that prints some text: …, and call Describe("hello").
  2. Add a fourth Area that takes one float64 radius and returns the area of a circle (3.14159 * radius * radius). Does Area(2.0) reach it, and does Area(2) still reach the square?
  3. Call Area(4, 2.0) and read the error, then fix the call.
  4. Write the two Half functions from this lesson and see the second one refused.

Learn more

  • Function declaration in the Rux Reference
  • Console — the PrintLine overloads you have been calling
  • Generic — one function for many types, instead of one overload per type