Operators
15 min read
Rust’s operators are similar to TypeScript/JavaScript, with a few important differences. Let’s explore arithmetic, comparison, logical, and bitwise operators.
Quick Overview
Section titled “Quick Overview”Most operators work the same way, but Rust:
- Has no
===(just==) - Requires same types for operations
- Has explicit bitwise operators
- No automatic type coercion
You’ll feel right at home with 90% of operators!
TypeScript/JavaScript Example
Section titled “TypeScript/JavaScript Example”// Arithmeticlet sum = 5 + 3; // 8let diff = 10 - 4; // 6let product = 3 * 4; // 12let quotient = 10 / 3; // 3.333...let remainder = 10 % 3; // 1
// Comparisonlet equal = 5 === 5; // truelet not_equal = 5 !== 6; // truelet greater = 10 > 5; // true
// Logicallet and = true && false; // falselet or = true || false; // truelet not = !true; // false
// Type coercion!let result = "5" + 3; // "53" (string)let result2 = "5" - 3; // 2 (number!)Key characteristic: Lots of implicit conversions
Rust Equivalent
Section titled “Rust Equivalent”// Arithmeticlet sum = 5 + 3; // 8let diff = 10 - 4; // 6let product = 3 * 4; // 12let quotient = 10 / 3; // 3 (integer division!)let remainder = 10 % 3; // 1let float_div = 10.0 / 3.0; // 3.333...
// Comparisonlet equal = 5 == 5; // true (no ===!)let not_equal = 5 != 6; // truelet greater = 10 > 5; // true
// Logicallet and = true && false; // falselet or = true || false; // truelet not = !true; // false
// No type coercion!// let result = "5" + 3; // Error: can't add &str and i32let result = "5".to_string() + &3.to_string(); // ExplicitKey characteristic: No implicit conversions, explicit types
Detailed Explanation
Section titled “Detailed Explanation”Arithmetic Operators
Section titled “Arithmetic Operators”let a = 10;let b = 3;
// Basic arithmeticlet sum = a + b; // 13let difference = a - b; // 7let product = a * b; // 30let quotient = a / b; // 3 (integer division!)let remainder = a % b; // 1
// Unary minuslet negative = -a; // -10
// Compound assignmentlet mut x = 5;x += 3; // x = x + 3 → 8x -= 2; // x = x - 2 → 6x *= 4; // x = x * 4 → 24x /= 3; // x = x / 3 → 8x %= 5; // x = x % 5 → 3Integer vs Float Division:
// Integer division (truncates)let a = 10 / 3; // 3
// Float divisionlet b = 10.0 / 3.0; // 3.333...let c = 10 as f64 / 3 as f64; // 3.333...Compare to TypeScript:
let a = 10 / 3; // 3.333... (always float)let b = Math.floor(10 / 3); // 3 (explicit truncation)Comparison Operators
Section titled “Comparison Operators”let a = 5;let b = 10;
// Equalitylet eq = a == b; // falselet ne = a != b; // true
// Orderinglet gt = a > b; // falselet lt = a < b; // truelet ge = a >= b; // falselet le = a <= b; // trueNo === in Rust!
// Rust only has ==let x = 5;let y = 5;let equal = x == y; // true
// TypeScript has == and ===// == does type coercion// === doesn'tIn Rust, == is always strict (no type coercion possible).
Logical Operators
Section titled “Logical Operators”let t = true;let f = false;
// ANDlet and1 = t && f; // falselet and2 = t && t; // true
// ORlet or1 = t || f; // truelet or2 = f || f; // false
// NOTlet not1 = !t; // falselet not2 = !f; // trueShort-circuit evaluation (like TypeScript):
fn expensive_check() -> bool { println!("Called!"); true}
let x = false && expensive_check(); // "Called!" not printedlet y = true || expensive_check(); // "Called!" not printedBitwise Operators
Section titled “Bitwise Operators”let a = 0b1100; // 12 in binarylet b = 0b1010; // 10 in binary
// Bitwise ANDlet and = a & b; // 0b1000 (8)
// Bitwise ORlet or = a | b; // 0b1110 (14)
// Bitwise XORlet xor = a ^ b; // 0b0110 (6)
// Bitwise NOT (! on integers, not just bools)let not = !a; // -13: inverts all 32 bits of the i32
// Left shiftlet left = a << 2; // 0b110000 (48)
// Right shiftlet right = a >> 2; // 0b0011 (3)
// Compound assignmentlet mut x = 5;x &= 3; // x = x & 3x |= 2; // x = x | 2x ^= 1; // x = x ^ 1x <<= 1; // x = x << 1x >>= 1; // x = x >> 1Compare to TypeScript:
let a = 12;let b = 10;
let and = a & b; // 8let or = a | b; // 14let xor = a ^ b; // 6let not = ~a; // -13 (two's complement)let left = a << 2; // 48let right = a >> 2; // 3Same syntax, same behavior!
Note: In Rust the same
!does both logical NOT (onbool) and bitwise NOT (on integers) — there is no separate~operator like JavaScript’s. For a signed integer,!ainverts all of its bits, which is equal to-1 - a(two’s complement). So withainferred asi32and equal to12,!ais-13, matching JavaScript’s~12. On an unsigned type the result stays non-negative:!0u8is255.
Key Differences from TypeScript
Section titled “Key Differences from TypeScript”1. No Type Coercion
Section titled “1. No Type Coercion”TypeScript:
let x = "5" + 3; // "53" (string)let y = "5" - 3; // 2 (number!)let z = "5" == 5; // true with ==, false with ===Rust:
// let x = "5" + 3; // Error: can't add &str and i32let x = format!("{}{}", "5", 3); // "53"
// let y = "5" - 3; // Error: can't subtractlet y = "5".parse::<i32>().unwrap() - 3; // 2
// let z = "5" == 5; // Error: mismatched typesWarning:
.unwrap()extracts the value from aResult/Option, but panics (crashes) if it is an error orNone. It is fine for examples and throwaway code where you know the input is valid, but in real code you should handle the error case explicitly withmatch, the?operator, or.expect("message")(covered in the error handling section).
2. Integer Division
Section titled “2. Integer Division”TypeScript:
let x = 10 / 3; // 3.333... (always float)Rust:
let x = 10 / 3; // 3 (integer division!)let y = 10.0 / 3.0; // 3.333... (float division)3. No === Operator
Section titled “3. No === Operator”TypeScript:
5 == "5"; // true (type coercion)5 === "5"; // false (strict equality)Rust:
5 == 5; // true// 5 == "5"; // Error: mismatched typesRust only has ==, but it’s always strict!
4. Overflow Behavior
Section titled “4. Overflow Behavior”TypeScript:
let x: number = Number.MAX_SAFE_INTEGER;x = x + 1; // Still works (but loses precision)Rust (debug mode):
let mut x: i32 = i32::MAX;// x = x + 1; // Panics! (overflow)x = x.wrapping_add(1); // Wraps aroundCommon Pitfalls
Section titled “Common Pitfalls”Pitfall 1: Integer Division Surprise
Section titled “Pitfall 1: Integer Division Surprise”Problem:
let average = (5 + 10) / 2; // Expected 7.5, got 7!Why: Integer division truncates.
Solution:
let average = (5.0 + 10.0) / 2.0; // 7.5let average = (5 + 10) as f64 / 2.0; // 7.5Pitfall 2: Comparing Different Types
Section titled “Pitfall 2: Comparing Different Types”Problem:
let x: i32 = 5;let y: i64 = 5;// if x == y { // Error: mismatched typesSolution:
let x: i32 = 5;let y: i64 = 5;if x as i64 == y { // Convert x to i64 println!("Equal!");}Pitfall 3: Expecting ===
Section titled “Pitfall 3: Expecting ===”Problem:
// if x === y { // Error: no operator ===Solution:
if x == y { // Use == println!("Equal!");}Pitfall 4: Overflow Without Handling
Section titled “Pitfall 4: Overflow Without Handling”Problem:
fn add_100(x: u8) -> u8 { x + 100 // Panics in debug mode when x is 200!}
fn main() { println!("{}", add_100(200));}Running this in debug mode panics with thread 'main' panicked at ...: attempt to add with overflow.
Note: If you instead write the overflow with two literals the compiler can see — for example
let y = 200u8 + 100;— it does not even compile: you geterror: this arithmetic operation will overflowat compile time. The runtime panic only happens when at least one operand isn’t a compile-time constant (herexarrives as a function argument).
Solution:
let x: u8 = 200;let y = x.saturating_add(100); // 255 (clamps)let z = x.wrapping_add(100); // 44 (wraps)let w = x.checked_add(100); // None (returns Option)Best Practices
Section titled “Best Practices”1. Use Explicit Types for Division
Section titled “1. Use Explicit Types for Division”Ambiguous:
let result = total / count; // Integer or float division?Explicit:
let result = total as f64 / count as f64; // Clearly float division2. Use Checked Arithmetic for User Input
Section titled “2. Use Checked Arithmetic for User Input”Risky:
let result = user_a + user_b; // Could overflow!Safe:
let result = user_a.checked_add(user_b) .expect("Overflow!");3. Use Ranges for Bounds Checks
Section titled “3. Use Ranges for Bounds Checks”Unlike Python, Rust has no chained comparisons (0 < x < 100 does not parse). You either combine two comparisons with &&, or — often clearer — use a range’s .contains() method:
Verbose:
if x > 0 && x < 100 { // ...}Clear (when appropriate):
if (0..100).contains(&x) { // ...}4. Parenthesize Complex Expressions
Section titled “4. Parenthesize Complex Expressions”Hard to read:
let result = a + b * c - d / e;Clear:
let result = a + (b * c) - (d / e);Real-World Example
Section titled “Real-World Example”Temperature Conversion
Section titled “Temperature Conversion”TypeScript:
function celsiusToFahrenheit(celsius: number): number { return (celsius * 9) / 5 + 32;}
function fahrenheitToCelsius(fahrenheit: number): number { return ((fahrenheit - 32) * 5) / 9;}
console.log(celsiusToFahrenheit(0)); // 32console.log(fahrenheitToCelsius(32)); // 0Rust:
fn celsius_to_fahrenheit(celsius: f64) -> f64 { (celsius * 9.0) / 5.0 + 32.0}
fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 { (fahrenheit - 32.0) * 5.0 / 9.0}
fn main() { println!("{}", celsius_to_fahrenheit(0.0)); // 32 println!("{}", fahrenheit_to_celsius(32.0)); // 0}Calculating Percentage
Section titled “Calculating Percentage”TypeScript:
function percentage(value: number, total: number): number { return (value / total) * 100;}
console.log(percentage(25, 100)); // 25console.log(percentage(1, 3)); // 33.333...Rust:
fn percentage(value: f64, total: f64) -> f64 { (value / total) * 100.0}
fn main() { println!("{}", percentage(25.0, 100.0)); // 25 println!("{:.2}", percentage(1.0, 3.0)); // 33.33}Bit Manipulation (Flags)
Section titled “Bit Manipulation (Flags)”// Permission flags using bitwise operationsconst READ: u8 = 0b0001; // 1const WRITE: u8 = 0b0010; // 2const EXECUTE: u8 = 0b0100; // 4const DELETE: u8 = 0b1000; // 8
fn main() { // Combine permissions let perms = READ | WRITE; // 0b0011 (3)
// Check permission let has_read = (perms & READ) != 0; // true let has_exec = (perms & EXECUTE) != 0; // false
// Add permission let perms = perms | EXECUTE; // 0b0111 (7)
// Remove permission let perms = perms & !WRITE; // 0b0101 (5)
println!("Permissions: {:04b}", perms); // 0101}Operator Precedence
Section titled “Operator Precedence”From highest to lowest:
- Unary:
-,! - Multiplicative:
*,/,% - Additive:
+,- - Shift:
<<,>> - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Comparison:
==,!=,<,>,<=,>= - Logical AND:
&& - Logical OR:
||
Use parentheses when in doubt!
let result = a + b * c; // b * c firstlet result = (a + b) * c; // a + b firstFurther Reading
Section titled “Further Reading”Official Documentation
Section titled “Official Documentation”Exercises
Section titled “Exercises”Exercise 1: Fix Division
Section titled “Exercise 1: Fix Division”Fix this to return a float:
fn average(a: i32, b: i32) -> f64 { (a + b) / 2 // Returns integer!}
fn main() { println!("{}", average(5, 10)); // Should be 7.5}Solution
fn average(a: i32, b: i32) -> f64 { (a + b) as f64 / 2.0}
fn main() { println!("{}", average(5, 10)); // 7.5}Exercise 2: Check Range
Section titled “Exercise 2: Check Range”Check if a number is in range 10-20 (inclusive):
fn in_range(x: i32) -> bool { // Implement}
fn main() { println!("{}", in_range(15)); // true println!("{}", in_range(25)); // false}Solution
fn in_range(x: i32) -> bool { x >= 10 && x <= 20}
fn main() { println!("{}", in_range(15)); // true println!("{}", in_range(25)); // false}Exercise 3: Swap Values
Section titled “Exercise 3: Swap Values”Swap two values without a temporary variable:
fn main() { let mut a = 5; let mut b = 10;
// Swap a and b // Hint: Rust gives you two idiomatic, overflow-safe ways
println!("a: {}, b: {}", a, b); // Should be a: 10, b: 5}Solution
The classic “arithmetic swap” (a = a + b; b = a - b; a = a - b;) works for small numbers but overflows and panics in debug when the values are near the integer bounds (e.g. a = i32::MAX). Rust gives you two safe, idiomatic alternatives instead:
fn main() { // Option 1: tuple destructuring assignment let mut a = 5; let mut b = 10; (a, b) = (b, a); println!("a: {}, b: {}", a, b); // a: 10, b: 5
// Option 2: std::mem::swap let mut c = 5; let mut d = 10; std::mem::swap(&mut c, &mut d); println!("c: {}, d: {}", c, d); // c: 10, d: 5}Warning: The arithmetic trick (
a = a + b; ...) is a classic interview puzzle, but it is fragile:a + bcan overflow. In debug builds Rust panics, in release builds it wraps to the wrong answer. Prefer tuple destructuring orstd::mem::swap.
Exercise 4: Check Power of Two
Section titled “Exercise 4: Check Power of Two”Check if a number is a power of 2 using bitwise operations:
fn is_power_of_two(n: u32) -> bool { // Hint: Powers of 2 have only one bit set // n & (n - 1) == 0 for powers of 2}
fn main() { println!("{}", is_power_of_two(8)); // true println!("{}", is_power_of_two(10)); // false println!("{}", is_power_of_two(16)); // true}Solution
fn is_power_of_two(n: u32) -> bool { n != 0 && (n & (n - 1)) == 0}
fn main() { println!("{}", is_power_of_two(8)); // true println!("{}", is_power_of_two(10)); // false println!("{}", is_power_of_two(16)); // true}Summary
Section titled “Summary”What you’ve learned:
- Arithmetic operators (
+,-,*,/,%) - Comparison operators (
==,!=,<,>, etc.) - Logical operators (
&&,||,!) - Bitwise operators (
&,|,^,<<,>>) - No type coercion in Rust
- Integer vs float division
- Operator precedence
Key differences from TypeScript:
| Aspect | TypeScript | Rust |
|---|---|---|
| Equality | ==, === | Only == (strict) |
| Division | Always float | Integer if integers |
| Type coercion | Implicit | Never! |
| Overflow | Loses precision (f64) | Panics (debug), wraps (release) |
Operators work mostly the same, but Rust is more strict!