Operators#

Rust’s operator surface covers arithmetic, comparison, logical, bitwise, reference (& *), range (a..b), assignment, and the try operator (?). Operators are also traits; you can implement them for custom types (std::ops::Add for +, Index for [], etc.).

/types`.

Arithmetic#

Operator

Meaning

+, -, *, /, %

the usual; division between integers truncates toward zero.

Integer overflow panics in debug and wraps in release. For explicit handling: checked_add (returns Option), saturating_add, wrapping_add, overflowing_add.

let n: u8 = 200;
let m: u8 = n.wrapping_add(100);         // 44
let opt = n.checked_add(100);             // None
let sat = n.saturating_add(100);          // 255

Relational#

== != < <= > >= work on any type implementing PartialEq / PartialOrd (most stdlib types). Floats are PartialOrd (not Ord) because of NaN.

1 == 1                  // true
"abc" < "abd"           // true (lex)

let v = vec![1, 2, 3];
v == vec![1, 2, 3]      // true; element-wise

Logical#

&& and || short-circuit; ! is unary. Operate on bool only; no truthiness.

if x > 0 && !done { /* … */ }

let either = a || b;     // a, b: bool

Bitwise#

let a = 0xff_u32 & 0x0f;          // 15
let b = 1u32 << 8;                 // 256
let c = !0u32;                     // 0xffffffff
let d = 0b1100 ^ 0b1010;           // 0b0110

Assignment#

=, plus the compound forms += -= *= /= %= &= |= ^= <<= >>=.

let mut x = 1;
x += 2;
x <<= 1;

Range#

for i in 0..10    { /* 0..=9 */ }
for i in 0..=10   { /* 0..=10 */ }

let sub = &xs[1..3];

Ranges are values themselves (std::ops::Range), so the operator can name them, pass them, and iterate them later.

The try operator (?)#

? is go-tos for propagating errors. Applied to a Result: unwrap on Ok, return Err(From::from(e)) on Err. Applied to an Option: unwrap on Some, return None.

fn load(path: &str) -> std::io::Result<String> {
    let s = std::fs::read_to_string(path)?;       // bubble up io errors
    Ok(s.trim().to_string())
}

fn parse_port(s: &str) -> Option<u16> {
    let n = s.parse::<u32>().ok()?;
    (n <= 65535).then_some(n as u16)
}

The function’s return type must match the propagated type (or be convertible via From).

References and deref#

&x borrows; &mut x borrows mutably; *r dereferences.

let n = 10;
let r = &n;
let v = *r;                         // 10

The dot operator . autoderefs through any number of references, so s.len() works whether s is String, &String, &&String.

as#

Explicit primitive cast (numeric, pointer, raw pointer cast). Use as sparingly because it truncates silently on out-of-range conversions; TryFrom returns a Result.

let i: i32 = 1000;
let b: u8 = i as u8;                // truncates: 232

let b: u8 = u8::try_from(1000)?;     // returns Err

Operator overloading#

Implementing std::ops::Add, Mul, Sub, Neg, Index, Deref, etc. lets the operator’s types use operator syntax.

use std::ops::Add;

#[derive(Clone, Copy, Debug)]
struct Vec2 { x: f64, y: f64 }

impl Add for Vec2 {
    type Output = Vec2;
    fn add(self, other: Vec2) -> Vec2 {
        Vec2 { x: self.x + other.x, y: self.y + other.y }
    }
}

let v = Vec2 { x: 1.0, y: 2.0 } + Vec2 { x: 3.0, y: 4.0 };

Precedence#

Subset, highest to lowest. The operator parenthesises whenever in doubt.

Group

Operators

postfix

a.b, a(), a[], a?

unary

- ! & &mut * (prefix)

as

multiplicative

* / %

additive

+ -

shift

<< >>

bitwise

& then ^ then |

relational

< <= > >=

equality

== !=

logical

&& then ||

range

a..b, a..=b

assignment

= += -=

References#

  • Types for the values each operator accepts.

  • Control flow for ? inside if let / match.

  • Errors for Result and Option with ?.

  • OOP for implementing Add, Index, etc.

  • std::ops.