Operator Overloading
Operator overloading lets your struct types work with built-in operators like
+=, ==, and <. You opt in by implementing the matching trait — there is no
trait to declare, you just write the impl. This is what lets the stdlib's
multi-byte number types read naturally:
score += bonus;
if score >= target { win(); }
Compound Assignment Operators
These operators update the left operand in place. Implement the trait whose
method takes *self plus one right-hand operand:
| Operator | Trait | Method |
|---|---|---|
+= | AddAssign | add_assign |
-= | SubAssign | sub_assign |
*= | MulAssign | mul_assign |
/= | DivAssign | div_assign |
%= | RemAssign | rem_assign |
&= | BitAndAssign | bitand_assign |
|= | BitOrAssign | bitor_assign |
^= | BitXorAssign | bitxor_assign |
<<= | ShlAssign | shl_assign |
>>= | ShrAssign | shr_assign |
impl AddAssign for I32 {
far fn add_assign(far *self, other: far *I32) {
self.add(other); // update the receiver in place
}
}
The left operand must be a writable place (local, static, field, or deref).
Unlike the primitive operators, *= and /= accept any operand here (no
power-of-2 restriction) and <<=/>>= accept a runtime shift amount.
Comparison Operators
| Operators | Trait | Method |
|---|---|---|
==, != | PartialEq | eq(*self, rhs) -> bool |
<, <=, >, >= | PartialOrd | cmp(*self, rhs) -> i8 |
You only implement eq and cmp — !=, <=, >, and >= come for free.
cmp is a three-way compare: return a negative number when self < rhs, 0
when equal, and a positive number when self > rhs.
impl PartialEq for I32 { far fn eq(far *self, other: far *I32) -> bool { return self.cmp(other) == 0; } }
impl PartialOrd for I32 { far fn cmp(far *self, other: far *I32) -> i8 { /* ... */ } }
The result is an ordinary bool, so it composes with &&, ||, if, and
while like any other comparison.
if score >= target && lives > ZERO {
advance_level();
}
if score != high_score {
high_score.clone_from(&score);
}
On Your Own Structs
The same pattern works for any struct. Each method takes a pointer to self and
a pointer to the other operand; use far *self when the value lives in RAM,
*self otherwise (see near vs far self):
struct Vec2 { x: i16, y: i16 }
impl AddAssign for Vec2 {
fn add_assign(*self, other: *Vec2) {
self.x = self.x + other.x;
self.y = self.y + other.y;
}
}
impl PartialEq for Vec2 {
fn eq(*self, other: *Vec2) -> bool {
return self.x == other.x && self.y == other.y;
}
}
fn step(pos: *Vec2, vel: *Vec2) {
*pos += *vel;
if *pos == ORIGIN { reset(); }
}
Copying With Clone
Structs and arrays are passed by reference and are not copied by a bare =.
To copy one, implement Clone:
impl Clone for Player {} // empty body = copy every byte
impl Clone for Enemy { // custom copy logic
fn clone_from(*self, src: *Enemy) { /* ... */ }
}
Then copy in one of two ways:
dst.clone_from(&src); // copy into an existing value
let c = a.clone(); // create a new value from a
let grid2 = grid.clone(); // arrays clone without any impl
.clone()is allowed only as the initializer of alet, or assigned to an existing struct/array.clone_fromwrites into*self, which must be writable.- Arrays clone automatically — you do not (and cannot) write
impl Clonefor an array type.
Limitations
- No value-producing form.
let c = a + bis not available for structs — use the in-place form:c = a; c += b;. - One operand type per operator. To combine a struct with a different type
(e.g. add a
u16to anI32), call a named method likescore.add_u16(1)instead of+=. &&,||,!work only onbooland cannot be overloaded.- Indexing (
[]) and dereference (*) operators cannot be overloaded.
See Also
- Structs — methods, and near vs far
self. - Traits — user-defined traits and dynamic dispatch.
- Math Library —
I32/U32/F32, the ready-made overloaded number types.