Complete Guide to Rust Operators

Introduction to Rust Operators

Operators in Rust are symbols that perform specific operations on one, two, or three operands and produce a result. Rust provides a rich set of operators that follow familiar patterns from C and C++ but with additional safety guarantees and some unique features. Understanding operators is fundamental to writing expressive and efficient Rust code.

Key Concepts

  • Operator Precedence: Determines the order of evaluation
  • Operator Associativity: Determines evaluation order for same-precedence operators
  • Overloading: Many operators can be overloaded via traits
  • Safety: Rust operators include bounds checking and overflow protection
  • Zero-cost: Operators compile to efficient machine code

1. Arithmetic Operators

Basic Arithmetic

fn main() { // Addition let sum = 5 + 10; let sum_float = 5.2 + 3.8; println!("Sum: {} (int), {} (float)", sum, sum_float); // Subtraction let difference = 95.5 - 4.3; let negative = 5 - 10; println!("Difference: {}, Negative: {}", difference, negative); // Multiplication let product = 4 * 30; let product_float = 2.5 * 4.0; println!("Product: {} (int), {} (float)", product, product_float); // Division let quotient = 56.7 / 32.2; let integer_division = 5 / 3; // Truncates to 1 let float_division = 5.0 / 3.0; // 1.666... println!("Quotient: {}", quotient); println!("Integer division: {}", integer_division); println!("Float division: {}", float_division); // Remainder (Modulo) let remainder = 43 % 5; let remainder_float = 43.5 % 5.0; // Works with floats println!("Remainder: {} (int), {} (float)", remainder, remainder_float); // Negation let neg = -5; let neg_float = -3.14; println!("Negation: {} (int), {} (float)", neg, neg_float); }

Compound Assignment

fn main() { let mut x = 10; // Add and assign x += 5; println!("x += 5: {}", x); // 15 // Subtract and assign x -= 3; println!("x -= 3: {}", x); // 12 // Multiply and assign x *= 2; println!("x *= 2: {}", x); // 24 // Divide and assign x /= 4; println!("x /= 4: {}", x); // 6 // Remainder and assign x %= 4; println!("x %= 4: {}", x); // 2 // Works with floats too let mut y = 10.0; y += 2.5; y *= 2.0; y /= 3.0; println!("Float operations: {}", y); }

Overflow Behavior

fn main() { // In debug builds, arithmetic overflow panics let mut x: u8 = 255; // x = x + 1; // This would panic in debug mode // In release builds, it wraps around // Use explicit methods for controlled behavior // Checked operations (returns Option) let checked = 255u8.checked_add(1); println!("Checked add: {:?}", checked); // None // Wrapping operations let wrapped = 255u8.wrapping_add(1); println!("Wrapping add: {}", wrapped); // 0 // Saturating operations (clamps to bounds) let saturated = 255u8.saturating_add(1); println!("Saturating add: {}", saturated); // 255 // Overflowing operations (returns result and overflow flag) let (result, overflowed) = 255u8.overflowing_add(1); println!("Overflowing add: {}, overflowed: {}", result, overflowed); // Similar methods for other operations println!("Checked sub: {:?}", 0u8.checked_sub(1)); println!("Wrapping mul: {}", 255u8.wrapping_mul(2)); println!("Saturating pow: {}", 10u32.saturating_pow(10)); }

2. Comparison Operators

Equality and Inequality

fn main() { let a = 5; let b = 10; let c = 5; // Equal to println!("a == b: {}", a == b); // false println!("a == c: {}", a == c); // true // Not equal to println!("a != b: {}", a != b); // true println!("a != c: {}", a != c); // false // Works with different types? No, types must match // println!("{}", 5 == 5.0); // Error: mismatched types // Can compare with casting println!("{}", 5 == 5.0 as i32); // true // Comparing strings let s1 = String::from("hello"); let s2 = String::from("world"); let s3 = String::from("hello"); println!("s1 == s2: {}", s1 == s2); // false println!("s1 == s3: {}", s1 == s3); // true println!("s1 != s2: {}", s1 != s2); // true // Comparing structs (requires PartialEq) #[derive(PartialEq)] struct Point { x: i32, y: i32, } let p1 = Point { x: 10, y: 20 }; let p2 = Point { x: 10, y: 20 }; let p3 = Point { x: 5, y: 25 }; println!("p1 == p2: {}", p1 == p2); // true println!("p1 == p3: {}", p1 == p3); // false }

Ordering Comparisons

fn main() { let a = 5; let b = 10; let c = 5; // Greater than println!("a > b: {}", a > b); // false println!("b > a: {}", b > a); // true // Less than println!("a < b: {}", a < b); // true println!("b < a: {}", b < a); // false // Greater than or equal to println!("a >= b: {}", a >= b); // false println!("a >= c: {}", a >= c); // true // Less than or equal to println!("a <= b: {}", a <= b); // true println!("a <= c: {}", a <= c); // true // Chaining comparisons (not allowed in Rust) // if 5 < a < 10 {} // Error // Use && instead if 5 < a && a < 10 { println!("a is between 5 and 10"); } // Comparing floats (be careful with precision) let x = 0.1 + 0.2; let y = 0.3; // Direct comparison might fail due to precision println!("x == y: {}", x == y); // false // Use epsilon comparison let epsilon = f64::EPSILON; println!("Approximately equal: {}", (x - y).abs() < epsilon); }

Comparing Custom Types

use std::cmp::Ordering; #[derive(Debug, PartialEq, Eq)] struct Person { name: String, age: u32, } // Implement PartialOrd for custom ordering impl PartialOrd for Person { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) // Delegate to Ord implementation } } impl Ord for Person { fn cmp(&self, other: &Self) -> Ordering { // First compare by age, then by name match self.age.cmp(&other.age) { Ordering::Equal => self.name.cmp(&other.name), other => other, } } } fn main() { let alice = Person { name: "Alice".to_string(), age: 30, }; let bob = Person { name: "Bob".to_string(), age: 25, }; let charlie = Person { name: "Charlie".to_string(), age: 30, }; println!("Alice < Bob: {}", alice < bob); // false (Alice older) println!("Bob < Alice: {}", bob < alice); // true println!("Alice < Charlie: {}", alice < charlie); // true (Alice before Charlie) // Using cmp for detailed ordering match alice.cmp(&bob) { Ordering::Less => println!("Alice is younger"), Ordering::Equal => println!("Same age"), Ordering::Greater => println!("Alice is older"), } }

3. Logical Operators

Boolean Logic

fn main() { let t = true; let f = false; // Logical AND (&&) println!("t && t: {}", t && t); // true println!("t && f: {}", t && f); // false println!("f && t: {}", f && t); // false println!("f && f: {}", f && f); // false // Logical OR (||) println!("t || t: {}", t || t); // true println!("t || f: {}", t || f); // true println!("f || t: {}", f || t); // true println!("f || f: {}", f || f); // false // Logical NOT (!) println!("!t: {}", !t); // false println!("!f: {}", !f); // true // Short-circuit evaluation fn expensive_computation() -> bool { println!("Computing..."); true } // Second operand not evaluated because first is false let result = false && expensive_computation(); println!("Result: {}", result); // false, no "Computing..." printed // Second operand not evaluated because first is true let result = true || expensive_computation(); println!("Result: {}", result); // true, no "Computing..." printed // Chaining logical operators let x = 5; let y = 10; let z = 15; if x < y && y < z && x > 0 { println!("All conditions are true"); } }

Bitwise Logical Operators

fn main() { let a = 0b1100; // 12 let b = 0b1010; // 10 // Bitwise AND (&) let and = a & b; // 0b1000 (8) println!("a & b = {:04b} ({})", and, and); // Bitwise OR (|) let or = a | b; // 0b1110 (14) println!("a | b = {:04b} ({})", or, or); // Bitwise XOR (^) let xor = a ^ b; // 0b0110 (6) println!("a ^ b = {:04b} ({})", xor, xor); // Bitwise NOT (!) let not_a = !a & 0b1111; // Mask to 4 bits: 0b0011 (3) println!("!a = {:04b} ({})", not_a, not_a); // Compound bitwise assignment let mut x = 0b1100; x &= 0b1010; // x = x & 0b1010 println!("x &= 0b1010: {:04b}", x); x |= 0b0011; println!("x |= 0b0011: {:04b}", x); x ^= 0b1111; println!("x ^= 0b1111: {:04b}", x); }

4. Bitwise Shift Operators

Shift Operations

fn main() { let x: u8 = 0b0001_0000; // 16 // Left shift (<<) let left = x << 2; // 0b0100_0000 (64) println!("x << 2 = {:08b} ({})", left, left); // Right shift (>>) let right = x >> 2; // 0b0000_0100 (4) println!("x >> 2 = {:08b} ({})", right, right); // Shifting with different types let y: i8 = -16; // 0b1111_0000 in two's complement let right_signed = y >> 2; // Arithmetic shift preserves sign println!("y >> 2 = {:08b} ({})", right_signed as u8, right_signed); // Compound shift assignment let mut z = 0b0001_0000; z <<= 2; println!("z <<= 2: {:08b}", z); z >>= 1; println!("z >>= 1: {:08b}", z); // Practical example: extracting bits let byte = 0b1010_1100; // Get high nibble let high = byte >> 4; println!("High nibble: {:04b}", high); // Get low nibble let low = byte & 0b1111; println!("Low nibble: {:04b}", low); }

Shift with Overflow

fn main() { // Shifting beyond bit width let x: u8 = 1; // In debug builds, shifting >= bit width panics // let too_far = x << 8; // Panics in debug mode // Use checked shifts let checked = x.checked_shl(8); println!("Checked shift: {:?}", checked); // None // Wrapping shifts let wrapped = x.wrapping_shl(8); println!("Wrapping shift: {}", wrapped); // 1 (shifts modulo bit width) // Saturating shifts (not available, but can implement) fn saturating_shl(x: u8, shift: u32) -> u8 { if shift >= 8 { u8::MAX } else { x << shift } } println!("Saturating shift: {}", saturating_shl(1, 8)); // 255 }

5. Range Operators

Range Types

fn main() { // Range (start..end) - exclusive of end let range = 1..5; // 1, 2, 3, 4 for i in range { println!("Range: {}", i); } // Range inclusive (start..=end) let inclusive = 1..=5; // 1, 2, 3, 4, 5 for i in inclusive { println!("Inclusive: {}", i); } // Range from (start..) let from = 3..; for i in from.take(5) { println!("From 3: {}", i); } // Range to (..end) let to = ..5; for i in to.start.unwrap_or(0)..5 { println!("To 5: {}", i); } // Full range (..) let full = ..; // Using ranges for slicing let arr = [1, 2, 3, 4, 5]; let slice = &arr[1..4]; // [2, 3, 4] println!("Slice: {:?}", slice); let slice_to = &arr[..3]; // [1, 2, 3] println!("Slice to: {:?}", slice_to); let slice_from = &arr[2..]; // [3, 4, 5] println!("Slice from: {:?}", slice_from); let slice_full = &arr[..]; // [1, 2, 3, 4, 5] println!("Full slice: {:?}", slice_full); }

Range Patterns

fn main() { let score = 85; // Using ranges in match patterns match score { 0..=59 => println!("F"), 60..=69 => println!("D"), 70..=79 => println!("C"), 80..=89 => println!("B"), 90..=100 => println!("A"), _ => println!("Invalid score"), } // Ranges in if let let value = Some(42); if let Some(x @ 1..=50) = value { println!("Small number: {}", x); } // Ranges with characters let c = 'e'; match c { 'a'..='z' => println!("Lowercase letter"), 'A'..='Z' => println!("Uppercase letter"), '0'..='9' => println!("Digit"), _ => println!("Other character"), } }

6. Type Testing Operators

Type Casting

fn main() { // 'as' operator for primitive casting let int_val: i32 = 42; let float_val = int_val as f64; let byte_val = int_val as u8; println!("int: {}, float: {}, byte: {}", int_val, float_val, byte_val); // Potential loss of data let large = 1000; let small = large as u8; // Wraps to 232 (1000 - 256*3) println!("1000 as u8 = {}", small); // Boolean to integer let true_val = true as u8; // 1 let false_val = false as u8; // 0 println!("true as u8: {}, false as u8: {}", true_val, false_val); // Character to integer let char_val = 'A'; let ascii = char_val as u8; // 65 println!("'A' as u8: {}", ascii); // Pointer casts (unsafe) let x = 42; let ptr = &x as *const i32; let addr = ptr as usize; println!("Pointer address: 0x{:x}", addr); }

Type Inspection

use std::any::type_name; fn type_of<T>(_: &T) -> &'static str { type_name::<T>() } fn main() { let x = 5; let y = 3.14; let z = "hello"; let w = vec![1, 2, 3]; println!("x is of type: {}", type_of(&x)); println!("y is of type: {}", type_of(&y)); println!("z is of type: {}", type_of(&z)); println!("w is of type: {}", type_of(&w)); // Using std::any for runtime type inspection use std::any::Any; let value: Box<dyn Any> = Box::new(42); if let Some(number) = value.downcast_ref::<i32>() { println!("It's an i32: {}", number); } if let Some(string) = value.downcast_ref::<String>() { println!("It's a String: {}", string); } else { println!("Not a String"); } }

7. Dereference Operators

Dereferencing

fn main() { let x = 5; let y = &x; // y is a reference to x // Dereference operator (*) println!("x = {}, *y = {}", x, *y); // Mutable reference let mut z = 10; let w = &mut z; *w += 5; // Modify through dereference println!("z after modification: {}", z); // Dereferencing with method calls (auto-deref) let s = String::from("hello"); let s_ref = &s; // These are equivalent due to auto-deref println!("Length: {}", s_ref.len()); // Method call auto-derefs println!("Length: {}", (*s_ref).len()); // Explicit deref // Deref coercion fn takes_str(s: &str) { println!("Got: {}", s); } let string = String::from("hello"); takes_str(&string); // &String coerces to &str // Box dereferencing let boxed = Box::new(5); println!("Boxed value: {}", *boxed); }

Smart Pointer Dereferencing

use std::ops::Deref; use std::rc::Rc; // Custom smart pointer struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } } fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y); // *y works due to Deref impl // Rc dereferencing let rc = Rc::new(10); println!("Rc value: {}", *rc); // Chained deref let rc_box = Rc::new(MyBox::new(15)); println!("Chained deref: {}", **rc_box); }

8. Operator Overloading

Arithmetic Operator Overloading

use std::ops::{Add, Sub, Mul, Div}; #[derive(Debug, Clone, Copy)] struct Point { x: i32, y: i32, } // Add operator (+) impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } // Add with different types impl Add<i32> for Point { type Output = Point; fn add(self, scalar: i32) -> Point { Point { x: self.x + scalar, y: self.y + scalar, } } } // Subtract operator (-) impl Sub for Point { type Output = Point; fn sub(self, other: Point) -> Point { Point { x: self.x - other.x, y: self.y - other.y, } } } // Multiply operator (*) impl Mul<i32> for Point { type Output = Point; fn mul(self, scalar: i32) -> Point { Point { x: self.x * scalar, y: self.y * scalar, } } } fn main() { let p1 = Point { x: 10, y: 20 }; let p2 = Point { x: 5, y: 15 }; let p3 = p1 + p2; println!("p1 + p2 = {:?}", p3); let p4 = p1 - p2; println!("p1 - p2 = {:?}", p4); let p5 = p1 + 5; println!("p1 + 5 = {:?}", p5); let p6 = p1 * 2; println!("p1 * 2 = {:?}", p6); }

Bitwise Operator Overloading

use std::ops::{BitAnd, BitOr, BitXor, Not}; #[derive(Debug, Clone, Copy, PartialEq)] struct Flags(u8); impl BitAnd for Flags { type Output = Flags; fn bitand(self, other: Flags) -> Flags { Flags(self.0 & other.0) } } impl BitOr for Flags { type Output = Flags; fn bitor(self, other: Flags) -> Flags { Flags(self.0 | other.0) } } impl BitXor for Flags { type Output = Flags; fn bitxor(self, other: Flags) -> Flags { Flags(self.0 ^ other.0) } } impl Not for Flags { type Output = Flags; fn not(self) -> Flags { Flags(!self.0) } } fn main() { let read = Flags(0b001); let write = Flags(0b010); let execute = Flags(0b100); let read_write = read | write; println!("read | write = {:03b}", (read_write).0); let all = read | write | execute; println!("all permissions = {:03b}", all.0); let without_read = all & !read; println!("all without read = {:03b}", without_read.0); let toggle = read ^ write; println!("read ^ write = {:03b}", toggle.0); }

Index Operator Overloading

use std::ops::{Index, IndexMut}; struct Matrix { data: Vec<Vec<i32>>, rows: usize, cols: usize, } impl Matrix { fn new(rows: usize, cols: usize) -> Self { Matrix { data: vec![vec![0; cols]; rows], rows, cols, } } } // Immutable indexing impl Index<usize> for Matrix { type Output = Vec<i32>; fn index(&self, row: usize) -> &Vec<i32> { &self.data[row] } } // Mutable indexing impl IndexMut<usize> for Matrix { fn index_mut(&mut self, row: usize) -> &mut Vec<i32> { &mut self.data[row] } } // Two-dimensional indexing impl Index<(usize, usize)> for Matrix { type Output = i32; fn index(&self, index: (usize, usize)) -> &i32 { let (row, col) = index; &self.data[row][col] } } impl IndexMut<(usize, usize)> for Matrix { fn index_mut(&mut self, index: (usize, usize)) -> &mut i32 { let (row, col) = index; &mut self.data[row][col] } } fn main() { let mut mat = Matrix::new(3, 3); // Use IndexMut to set values mat[0][0] = 1; mat[0][1] = 2; mat[1][0] = 3; mat[1][1] = 4; // Use Index to get values println!("mat[0][1] = {}", mat[0][1]); // Use tuple indexing mat[(2, 2)] = 5; println!("mat[(2, 2)] = {}", mat[(2, 2)]); // Print matrix for i in 0..mat.rows { for j in 0..mat.cols { print!("{} ", mat[(i, j)]); } println!(); } }

9. Assignment Operators

Basic Assignment

fn main() { // Simple assignment (=) let x = 5; let y = x; // Copy (for Copy types) // Move semantics for non-Copy types let s1 = String::from("hello"); let s2 = s1; // s1 is moved to s2 // println!("{}", s1); // Error: s1 no longer valid // Clone for explicit copy let s3 = String::from("hello"); let s4 = s3.clone(); println!("s3: {}, s4: {}", s3, s4); // Both valid // Destructuring assignment let (a, b, c) = (1, 2, 3); println!("a: {}, b: {}, c: {}", a, b, c); // Struct destructuring struct Point { x: i32, y: i32, } let p = Point { x: 10, y: 20 }; let Point { x, y } = p; println!("x: {}, y: {}", x, y); }

Compound Assignment Traits

use std::ops::{AddAssign, SubAssign, MulAssign, DivAssign}; #[derive(Debug, Clone, Copy)] struct Vector { x: i32, y: i32, } impl AddAssign for Vector { fn add_assign(&mut self, other: Vector) { self.x += other.x; self.y += other.y; } } impl SubAssign for Vector { fn sub_assign(&mut self, other: Vector) { self.x -= other.x; self.y -= other.y; } } impl MulAssign<i32> for Vector { fn mul_assign(&mut self, scalar: i32) { self.x *= scalar; self.y *= scalar; } } fn main() { let mut v1 = Vector { x: 10, y: 20 }; let v2 = Vector { x: 5, y: 15 }; v1 += v2; println!("v1 after +=: {:?}", v1); v1 -= v2; println!("v1 after -=: {:?}", v1); v1 *= 2; println!("v1 after *=: {:?}", v1); }

10. Operator Precedence and Associativity

Precedence Table

fn main() { // Operator precedence (highest to lowest) // 1. Path and method calls (., ::, ()) // 2. Unary operators (!, -, *, &, &mut) // 3. As and .. (as, ..) // 4. Multiplicative (*, /, %) // 5. Additive (+, -) // 6. Shift (<<, >>) // 7. Bitwise AND (&) // 8. Bitwise XOR (^) // 9. Bitwise OR (|) // 10. Comparison (==, !=, <, >, <=, >=) // 11. Logical AND (&&) // 12. Logical OR (||) // 13. Range (.., ..=) // 14. Assignment and compound assignment (=, +=, etc.) let result = 5 + 3 * 4; // 5 + (3 * 4) = 17 println!("5 + 3 * 4 = {}", result); let result = (5 + 3) * 4; // (5 + 3) * 4 = 32 println!("(5 + 3) * 4 = {}", result); // Mixing bitwise and comparison let x = 5; let y = 10; let z = 15; // Bitwise AND has higher precedence than comparison let result = x & y == 0; // x & (y == 0) - probably not what you want println!("x & y == 0 = {}", result); // Use parentheses for clarity let result = (x & y) == 0; println!("(x & y) == 0 = {}", result); // Logical operators short-circuit let result = expensive_check() || cheap_check(); // Short-circuits } fn expensive_check() -> bool { println!("Expensive check"); false } fn cheap_check() -> bool { println!("Cheap check"); true }

Associativity Examples

fn main() { // Left-associative operators (most) let result = 10 - 5 - 2; // (10 - 5) - 2 = 3 println!("10 - 5 - 2 = {}", result); let result = 10 / 5 / 2; // (10 / 5) / 2 = 1 println!("10 / 5 / 2 = {}", result); // Right-associative operators // Assignment is right-associative let mut a = 5; let mut b = 10; let mut c = 15; a = b = c; // a = (b = c) println!("a = b = c: a = {}, b = {}, c = {}", a, b, c); // .. range is also tricky let range = 1..5..=10; // (1..5)..=10 - usually not what you want // Use parentheses: 1..(5..=10) if needed }

11. Special Operators

The ? Operator

use std::num::ParseIntError; fn parse_number(s: &str) -> Result<i32, ParseIntError> { let num = s.parse::<i32>()?; // Propagates error if any Ok(num * 2) } fn process_numbers() -> Result<(), ParseIntError> { let a = parse_number("10")?; let b = parse_number("20")?; let c = parse_number("30")?; println!("Results: {}, {}, {}", a, b, c); Ok(()) } // With Option fn find_first_even(numbers: &[i32]) -> Option<&i32> { let first = numbers.get(0)?; // Returns None if index out of bounds if first % 2 == 0 { Some(first) } else { numbers.get(1) // May return None } } fn main() { match process_numbers() { Ok(()) => println!("Success!"), Err(e) => println!("Error: {}", e), } let nums = vec![1, 3, 5, 7]; match find_first_even(&nums) { Some(x) => println!("Found even: {}", x), None => println!("No even numbers"), } }

The ..= and .. Operators (Revisited)

fn main() { // Inclusive range patterns in matches match 42 { 0..=50 => println!("Between 0 and 50"), 51..=100 => println!("Between 51 and 100"), _ => println!("Out of range"), } // Ranges in for loops for i in (0..5).rev() { println!("Reverse: {}", i); } // Step by (using step_by) for i in (0..=10).step_by(2) { println!("Even: {}", i); } // Ranges with custom types #[derive(PartialEq, PartialOrd)] struct Grade(char); let grade = Grade('B'); // Can't use range with custom type without more traits // if grade >= Grade('A') && grade <= Grade('C') { // println!("Passing grade"); // } }

12. Practical Examples

Bit Manipulation Utilities

// Bit manipulation helper fn get_bit(value: u8, n: u8) -> bool { (value & (1 << n)) != 0 } fn set_bit(value: u8, n: u8) -> u8 { value | (1 << n) } fn clear_bit(value: u8, n: u8) -> u8 { value & !(1 << n) } fn toggle_bit(value: u8, n: u8) -> u8 { value ^ (1 << n) } fn main() { let mut flags: u8 = 0b0000_0000; flags = set_bit(flags, 2); flags = set_bit(flags, 5); println!("Flags after setting: {:08b}", flags); println!("Bit 2 is set: {}", get_bit(flags, 2)); println!("Bit 3 is set: {}", get_bit(flags, 3)); flags = clear_bit(flags, 2); println!("After clearing bit 2: {:08b}", flags); flags = toggle_bit(flags, 5); println!("After toggling bit 5: {:08b}", flags); }

Mathematical Operations

// Complex number operations use std::ops::{Add, Mul}; #[derive(Debug, Clone, Copy)] struct Complex { real: f64, imag: f64, } impl Complex { fn new(real: f64, imag: f64) -> Self { Complex { real, imag } } fn magnitude(&self) -> f64 { (self.real * self.real + self.imag * self.imag).sqrt() } } impl Add for Complex { type Output = Complex; fn add(self, other: Complex) -> Complex { Complex { real: self.real + other.real, imag: self.imag + other.imag, } } } impl Mul for Complex { type Output = Complex; fn mul(self, other: Complex) -> Complex { Complex { real: self.real * other.real - self.imag * other.imag, imag: self.real * other.imag + self.imag * other.real, } } } fn main() { let c1 = Complex::new(3.0, 4.0); let c2 = Complex::new(1.0, 2.0); let sum = c1 + c2; let product = c1 * c2; println!("c1: {:?}, magnitude: {}", c1, c1.magnitude()); println!("c2: {:?}", c2); println!("Sum: {:?}", sum); println!("Product: {:?}", product); }

Conclusion

Rust's operators provide a comprehensive set of tools for manipulating data:

Key Takeaways

  1. Arithmetic Operators: +, -, *, /, % for basic math
  2. Comparison Operators: ==, !=, <, >, <=, >= for ordering
  3. Logical Operators: &&, ||, ! for boolean logic
  4. Bitwise Operators: &, |, ^, !, <<, >> for bit manipulation
  5. Range Operators: .., ..=, .., .. for sequences and slicing
  6. Assignment Operators: =, +=, -=, etc. for modifying variables
  7. Dereference Operator: * for accessing referenced values
  8. Type Casting: as for explicit type conversions
  9. Error Propagation: ? for concise error handling

Operator Overloading

  • Implement traits from std::ops to overload operators
  • Common traits: Add, Sub, Mul, Div, Rem, Neg
  • Bitwise: BitAnd, BitOr, BitXor, Not, Shl, Shr
  • Compound assignment: AddAssign, SubAssign, etc.
  • Indexing: Index, IndexMut

Safety Features

  • Overflow checking in debug builds
  • Checked, wrapping, and saturating methods
  • Type safety prevents mixed-type operations
  • Borrow checker ensures reference safety

Best Practices

  1. Use parentheses for complex expressions to improve readability
  2. Prefer checked operations when overflow is possible
  3. Implement operators only when semantically meaningful
  4. Use bitwise operators for flags and bit manipulation
  5. Leverage operator overloading for domain-specific types
  6. Be careful with floating-point comparisons due to precision issues

Rust's operator system balances expressiveness with safety, providing familiar syntax while preventing common programming errors through compile-time checks and runtime safety features.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper