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;
}
}
selfis the left operand, borrowed. The one other parameter is the right operand, of any type:Money * int64above, with noMoney * Moneyand noint64 * 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 withcannot pass 'Money' to parameter of type '&Money'.
| You write | The compiler calls | self | Right operand |
|---|---|---|---|
rent + food | + | rent | food |
rent * 3 | * | rent | 3 |
rent == food | == | rent | food |
food < rent | < | food | rent |
sum += food | +, then assigns | sum | food |
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
| Overloadable | Operators |
|---|---|
| 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 overloadable | Why |
|---|---|
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 write | The compiler uses |
|---|---|
a != b | !(a == b) |
a > b | b < a |
a <= b | a < b || a == b |
a >= b | b < 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.
<=isa < 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:
| Kind | Equal when |
|---|---|
| Structure | every field is equal, compared in declaration order |
| Tuple | every element is equal |
| Array | every element is equal |
| Variant | the 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
- Comparison — the built-in comparison operators
- Indexers —
[]and[]= - Core interfaces —
EquatableandComparable, which are methods rather than operators - Learn: Operator overload, Derived operator, Structural equality
Core Interfaces
The interfaces the standard packages build on: Equatable, Comparable and Ordering, Hashable, Iterator and Iterable in Core; Display and Debug in Text.
Indexers
A type is indexed by declaring func [] to read one element and func []= to write one. The index may be of any type, and overloads are separated by it.