Operators

A type gives a binary operator a meaning by declaring a function whose name is the operator, in an extend block. a + b with an a of that type is then a call to that function. Operators are not interfaces: there is no Addable to implement, only the operator function itself.

Declaring an operator

extend T {
    func op(self: &T, right: R) -> Result { … }
}
struct Money {
    cents: int64;
}

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

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

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

    func <(self: &Money, other: Money) -> bool {
        return self.cents < other.cents;
    }
}
  • self is the left operand, borrowed. The one other parameter is the right operand, of any type: Money * int64 above, with no Money * Money and no int64 * Money.
  • The result is any type. Comparisons conventionally return bool.
  • Overloads of one operator are separated by the right operand's type, like any overload.
  • A right operand taken by value accepts anything, including a value built on the spot. One taken by reference, other: &Money, needs a named value: rent == Money { cents: 5 } then fails with cannot pass 'Money' to parameter of type '&Money'.
You writeThe compiler callsselfRight operand
rent + food+rentfood
rent * 3*rent3
rent == food==rentfood
food < rent<foodrent
sum += food+, then assignssumfood

A compound assignment a op= b uses the declared op and assigns its result to a, so declaring + makes += work as well.

An operator that is not declared does not exist for the type, and the error names both operands:

error: operator '/' cannot combine left operand 'Money' with right operand 'int'

Which operators

OverloadableOperators
Arithmetic+ - * / %
Bitwise& | ^
Shift<< >> >>>
Comparison== != < <= > >=
Logical&& ||
Compound assignment+= -= … — through the binary operator
Indexing[] and []= — see Indexers

An overloaded && or || is an ordinary call: both operands are evaluated, with no short-circuit.

Not overloadableWhy
unary -, !, ~, *, @, ++, --built in for primitives and pointers only
= and <-copy and move are special operations
??operator '??' is built in and cannot be declared or overloaded
as, is, .., ?, .part of the language, not of any type

A unary operator on a structure is refused even if a function named after it is declared: -money fails with operator '-' requires a numeric operand, but found 'Money'.

Derived comparisons

A structure declares at most two comparison operators. From == the compiler derives !=, and from < together with == it derives the other three:

You writeThe compiler uses
a != b!(a == b)
a > bb < a
a <= ba < b || a == b
a >= bb < a || a == b
flowchart LR
    eq["== (declared)"] --> ne["!="]
    lt["< (declared)"] --> gt[">"]
    lt --> le["<="]
    eq --> le
    lt --> ge[">="]
    eq --> ge
  • Each operand is evaluated once, even though the derivation names it twice.
  • <= is a < b || a == b, not !(b < a). For a pair that is neither ordered nor equal, only the first is right, so a partial order stays correct.
  • A declared operator wins over its derivation. Declaring !=, >, <= or >= by hand is allowed, and is the one way the six can come to disagree.
  • Derivation works inside generic code too, after the type parameter is substituted.

When neither the operator nor what it derives from is declared, the error says so — here for a Money that declares == but not <:

error: operator '>' is not defined for 'Money'
  note: a struct is compared through the operators it declares, never by its representation
  help: declare '>' on 'Money', or the '<' it is derived from

Structural equality

Without any declaration, == and != already work on structures, tuples, fixed arrays and variants, and on any nesting of them. Two values are equal when they are equal part by part:

KindEqual when
Structureevery field is equal, compared in declaration order
Tupleevery element is equal
Arrayevery element is equal
Variantthe case is the same, and so is its payload

Each part is compared with its own == — a declared one where its type declares it, structural otherwise — so a structure holding a Money uses Money's == for that field. Floating-point fields compare by value: a NaN field makes two values unequal, and 0.0 equals -0.0. Padding bytes play no part.

A declared == on a structure replaces its structural equality. Every part must have an == of its own; a slice has none, so a structure with a char8[..] field has no structural equality:

error: structural equality for 'Named' is unavailable because element type 'char8[..]' has no '==' operator
  help: declare '==' on 'char8[..]' or compare the supported elements explicitly

There is no structural ordering. < and the rest are defined only by declaration: operator '<' is not defined for 'Point' for a structure, operator '<' is not defined for variant 'Shape' for a variant, and operator '<' is not defined for tuple '(int, int)' for a tuple.

See also