When Ferrous Metals Corrode, pt. XI


This installement of my Rust learning series deals with Chapter 12. Operator Overloading

I'm actually a bit wary of operator overloading, as it's easily ab/overused, and objects then behaving in a opaque "magical" way. Sometimes it's just clearer to spell a method out instead of binding it to an operator that only superficially relates to the action being carried out. So I will keep this post short.

There's a number of traits in the std::ops module that help with operator overloading – from std::ops::Add, std::ops::BitAnd, to std::ops::Shr for right shifting, etc.

Arithmetic and Bitwise Operators

Example of implementing + for a complex type Complex<T>:

use std::ops::Add;

impl<T> Add for Complex<T>
    T: Add<Output = T>,
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        Complex {
            re: self.re + rhs.re,
            im: self.im + rhs.im,

The Complex<T> type has a type parameter (presumably numeric); we implement Add for this generic type. We also say that we implement Add for T's that can be added to themselves.

Unary operators

Rusts signed ints support the negative sign -, and bools as well as ints support negation ! – for ints this results in bitwise complement.

Binary operators

Binary arithmetic operators are supported by all numeric types. Ints and bools implement bitwise operators. The + operator can be used on Strings and &str slices, however &str mustn't be left-hand side (performs poorly). Should use write!() for building up strings in general.

Compound Assignment Operators

These are known as "Augmented assignment operators" in Python, i.e. things like x += 1. Rust defines a separate set of traits for them, from AddAssign to ShrAssign.

Equivalence Comparisons

The relevant trait here is std::cmp::PartialEq. It defines two methods .eq() and .ne(); the latter has a default implementation (negating eq()), so typically only need to implement one method – or derive it with #[derive(PartialEq)]

Side note, the trait is called PartialEq as the == operator does not implement full equivalence relations, as that doesn't work with floats as defined by IEEE (specifically, 0.0/0.0 needs to be NaN according to the standard, and NaN being unequal to everything else). If you don't care for floats there's an Eq trait as well (which too can be #[derive(Eq)]).

Ordered Comparisons

The only method that needs to be implemented for the PartialOrd trait is partial_cmp(), which needs to return one of the enum Ordering values Less, Equal, or Greater wrapped in an Option. In analog to equivalence above there's also an Ord trait which defines a method cmp(), which should return an Ordering.

Index and IndexMut

These implement indexing operations a[i]. Arrays support indexing directly, but for other types indexing usually is a shorthand for a.index(i), where .index() is from the Index trait. IndexMut comes into play where the expression is being borrowed mut or assigned. The index i does not have to be int btw., e.g. the HashMap collection implements Index<&str> to allow indexing by string.

Other Operators

Operators that can't be overloaded: "?" for error-checking, && and || which operate only on bools. The .. and ..= operators, the & and = operators. The func call f() can't be overloaded either (use a closure instead).

Overloading of * the . operator are handled in a later chapter.


Short post, as I'm not much of a fan of operator overloading outside of some specific use cases (maybe around custom quasi-numeric types or collections).