Overload
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.
// 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
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.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
- Add
Describe(value: char8[..])that printssome text: …, and callDescribe("hello"). - Add a fourth
Areathat takes onefloat64radius and returns the area of a circle (3.14159 * radius * radius). DoesArea(2.0)reach it, and doesArea(2)still reach the square? - Call
Area(4, 2.0)and read the error, then fix the call. - Write the two
Halffunctions from this lesson and see the second one refused.
Learn more
- Function declaration in the Rux Reference
- Console — the
PrintLineoverloads you have been calling - Generic — one function for many types, instead of one overload per type