5.6 Operators

Rust supports most standard operators familiar from C/C++.

  • Arithmetic: + (add), - (subtract), * (multiply), / (divide), % (remainder/modulo).
  • Comparison: == (equal), != (not equal), < (less than), > (greater than), <= (less than or equal), >= (greater than or equal). These return a bool.
  • Logical: && (logical AND, short-circuiting), || (logical OR, short-circuiting), ! (logical NOT). Operate on bool values.
  • Bitwise: & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), ! (bitwise NOT - unary, only for integers), << (left shift), >> (right shift). Operate on integer types. Right shifts on signed integers perform sign extension; on unsigned integers, they shift in zeros.
  • Assignment: = (simple assignment).
  • Compound Assignment: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=. Combines an operation with assignment (e.g., x += 1 is equivalent to x = x + 1).
  • Unary: - (negation for numbers), ! (logical NOT for bool, bitwise NOT for integers), & (borrow/reference), * (dereference).
  • Type Casting: as (e.g., let float_val = integer_val as f64;). Explicit casting is often required between numeric types.
  • Grouping: () changes evaluation order.
  • Access: . (member access for structs/tuples), [] (index access for arrays/slices/vectors).

Key Differences/Notes for C Programmers:

  1. No Increment/Decrement Operators: Rust does not have ++ or --. Use x += 1 or x -= 1 instead. This avoids ambiguities present in C regarding pre/post increment/decrement return values and side effects within expressions.
  2. Strict Type Matching: Binary operators (like +, *, &, ==) generally require operands of the exact same type. Implicit numeric promotions like in C (e.g., int + float) do not happen. You must explicitly cast using as.
    #![allow(unused)]
    fn main() {
    let a: i32 = 10;
    let b: u8 = 5;
    // let c = a + b; // Compile Error: mismatched types i32 and u8
    let c = a + (b as i32); // OK: b is explicitly cast to i32
    println!("c = {}", c);
    }
  3. No Ternary Operator: Rust does not have C’s condition ? value_if_true : value_if_false. Use an if expression instead, which is more readable and less prone to precedence errors:
    #![allow(unused)]
    fn main() {
    let condition = true;
    let result = if condition { 5 } else { 10 };
    println!("Result = {}", result);
    }
  4. Operator Overloading: You cannot create new custom operators, but you can overload existing operators (like +, -, *, ==) for your own custom types (structs, enums) by implementing corresponding traits from the std::ops module (e.g., Add, Sub, Mul, PartialEq). This allows operators to work intuitively with user-defined types like vectors or complex numbers.

In addition to the operators mentioned earlier, Rust uses & to create references or to specify that a type is a reference, and * to dereference a reference in order to access the original value it points to.

Operator Precedence: Largely follows C/C++ conventions (e.g., * and / before + and -, comparisons before logical operators). Use parentheses () to clarify or force a specific evaluation order when in doubt – clarity is usually preferred over relying on subtle precedence rules.