Interfaces · Lesson 12.8

Operator overload

Source
Define +, == and other operators for a type of your own.

The operators from the Operators part are not reserved for numbers. A type of your own can say what +, == or < mean for it, by declaring a function whose name is the operator. The point is not cleverness: money adds to money, and rent + food says that plainly where Money { cents: rent.cents + food.cents } buries it.

An operator is a function

Operator functions go in an ordinary extend block, next to the constructor:

// `self` is the left operand and the second parameter is the right one. Taking the right
// operand by value lets it be a value built on the spot, as in `rent == Money(120000)`.
// `other: &Money` works as well, but a borrow needs a named value on the right.
func +(self: &Money, other: Money) -> Money {
    return Money { cents: self.cents + other.cents };
}

The function's name is the operator itself. When the compiler meets rent + food with a Money on the left, it calls this function with self borrowing rent and other holding food:

You writeThe compiler callsselfRight operandResult
rent + food+rentfoodMoney
rent - food-rentfoodMoney
rent * 3*rent3Money
rent == Money(120000)==renta new Moneybool
food < rent<foodrentbool

Once + is declared, the compound form works too: sum += food adds to a var sum of type Money.

The two sides need not match

// The two sides need not have the same type. Money times a count makes sense; money times
// money does not, so only this one exists.
func *(self: &Money, count: int64) -> Money {
    return Money { cents: self.cents * count };
}

The second parameter's type is whatever the right operand should be. Money times a count makes sense; money times money does not, so rent * rent is simply not defined. The order matters as well: self is always the left operand, so this function makes rent * 3 work but not 3 * rent.

Comparisons answer with bool

// Comparisons answer with `bool`, not with Money.
func ==(self: &Money, other: Money) -> bool {
    return self.cents == other.cents;
}

func <(self: &Money, other: Money) -> bool {
    return self.cents < other.cents;
}

A declared == replaces the field-by-field comparison from Structural equality. For Money both give the same answer, but a type like the Fraction from Equatable could now make 1/2 == 2/4. And from these two, the compiler can work out !=, >, <= and >= — the subject of the next lesson.

Only what you declare

Money has no /, so rent / 2 is an error rather than something invented on Money's behalf. Each operator you leave out is a decision: there is no sensible meaning for money divided by money, and dividing by a count would raise the question of what to do with the leftover cent.

The program

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

Src/Main.rux
// The operators from the Operators part are not reserved for numbers. A type of your own can say
// what `+`, `==` or `<` mean for it, by declaring a function whose name is the operator.
//
// The point is not cleverness. Money adds to money, and `rent + food` says that plainly where
// `Money { cents: rent.cents + food.cents }` buries it. A declared `==` also replaces the
// field-by-field comparison from the previous lesson, so a type decides what "equal" means.
import Io::PrintLine;

// Money as a whole number of cents, so no fraction of a cent ever goes missing.
struct Money {
    cents: int64;
}

extend Money {
    func Money(cents: int64) -> Money {
        return Money { cents: cents };
    }

    // `self` is the left operand and the second parameter is the right one. Taking the right
    // operand by value lets it be a value built on the spot, as in `rent == Money(120000)`.
    // `other: &Money` works as well, but a borrow needs a named value on the right.
    func +(self: &Money, other: Money) -> Money {
        return Money { cents: self.cents + other.cents };
    }

    func -(self: &Money, other: Money) -> Money {
        return Money { cents: self.cents - other.cents };
    }

    // The two sides need not have the same type. Money times a count makes sense; money times
    // money does not, so only this one exists.
    func *(self: &Money, count: int64) -> Money {
        return Money { cents: self.cents * count };
    }

    // Comparisons answer with `bool`, not with Money.
    func ==(self: &Money, other: Money) -> bool {
        return self.cents == other.cents;
    }

    func <(self: &Money, other: Money) -> bool {
        return self.cents < other.cents;
    }
}

func Main() -> int {
    let rent = Money(120000);
    let food = Money(35050);

    // Each operator below calls one of the functions above.
    let total = rent + food;
    let left = rent - food;
    let quarter = rent * 3;
    PrintLine("rent + food = {} cents", total.cents);
    PrintLine("rent - food = {} cents", left.cents);
    PrintLine("rent * 3    = {} cents", quarter.cents);

    PrintLine("rent == 120000 cents: {}", rent == Money(120000));
    PrintLine("food < rent:          {}", food < rent);

    // Only the operators declared exist. Money has no `/`, so `rent / 2` is an error rather
    // than something invented on Money's behalf.
    return 0;
}

Run it

cd Examples/Interfaces/OperatorOverload
rux run
rent + food = 155050 cents
rent - food = 84950 cents
rent * 3    = 360000 cents
rent == 120000 cents: true
food < rent:          true

Common mistakes

Using an operator that was never declared.
rent / 2 fails with error: operator '/' cannot combine left operand 'Money' with right operand 'int'. Declare / if Money should have it.
Putting the operands the other way round.
* was declared with Money on the left, so 3 * rent fails with error: operator '*' cannot combine left operand 'int' with right operand 'Money'. Write rent * 3.
Borrowing the right operand.
Declare == as func ==(self: &Money, other: &Money) -> bool and rent == Money(120000) fails with error: cannot pass 'Money' to parameter of type '&Money': a borrow needs a named value, not one built on the spot. Take the right operand by value, as the lesson does.

Try it yourself

  1. Declare / taking an int64 count, and print rent / 3. What happens to the leftover cent?
  2. Use += in a loop to add up an array of Money values.
  3. Write a Vector { x: float64; y: float64; } with + for two vectors and * for a vector and a float64.

Learn more