Code Story

Chapter #3 – Operators

arithmetic, comparison, logical, bitwise, assignment, overloading

3.0  Prologue

Operators are the primitive computations a language provides on its built-in types. Most imperative languages share the same operator symbols, but differ in overflow semantics, precedence subtleties, and how far operator overloading is permitted.

3.1  Arithmetic Operators

The standard arithmetic operators are +, -, *, /, and % (remainder). Integer division truncates toward zero in all four languages. Overflow behavior differs significantly:
Language Debug build Release / production
Rust panic on overflow wrapping (two’s complement)
C++ (signed) undefined behavior undefined behavior
C++ (unsigned) modular wrap modular wrap
C# silent wrap (checked context: exception) silent wrap
Python arbitrary precision; no overflow arbitrary precision; no overflow
Rust provides explicit wrapping, saturating, and checked arithmetic methods (wrapping_add, saturating_add, checked_add) when the default panic is not desired.

3.2  Comparison Operators

Comparison operators produce a boolean: ==, !=, <, >, <=, >=. Total vs partial ordering: integers have a total order (every pair is comparable). Floating-point types do not - NaN != NaN and comparisons involving NaN always return false. Rust encodes this in the type system: f64 implements PartialOrd but not Ord, preventing floats from being used as sort keys without explicit handling. Equality vs identity: C# and Python distinguish value equality (==) from reference identity (object.ReferenceEquals / is). Rust and C++ have only value equality for scalar types; reference equality requires explicit pointer comparison.

3.3  Logical Operators

&& (and), || (or), ! (not) operate on boolean values in all four languages. Short-circuit evaluation: && does not evaluate its right operand if the left is false; || does not evaluate its right operand if the left is true. This is guaranteed by the language standard in all four languages and is commonly used to guard unsafe operations: // short-circuit prevents index-out-of-bounds if !v.is_empty() && v[0] > threshold { /* ... */ } // Python if lst and lst[0] > threshold: pass Python also provides the words and, or, not as operator alternatives. Unlike other languages, Python’s or and and return one of their operands, not a strict boolean.

3.4  Bitwise Operators

Bitwise operators work on the binary representation of integer values:
Operator Meaning Example
& AND 0b1100 & 0b1010 == 0b1000
| OR 0b1100 | 0b1010 == 0b1110
^ XOR 0b1100 ^ 0b1010 == 0b0110
! / ~ NOT (bitwise complement) !0u8 == 255
<< left shift 1 << 3 == 8
>> right shift 16 >> 2 == 4
Rust uses ! for bitwise NOT on integers (same token as logical NOT). C++ and C# use ~. Shifting by more bits than the type width is undefined behavior in C++ for signed types; Rust panics in debug and wraps in release.

3.5  Assignment Operators

= binds a value to a mutable variable. Compound assignment operators combine an operation with assignment: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=. In Rust, assignment moves or copies the value depending on whether the type implements Copy. In C++, assignment invokes the copy or move assignment operator. In C# and Python, assignment copies a reference for reference types; Python integers and strings are immutable so rebinding is effectively copying. Rust does not make assignment an expression (it returns ()), preventing the classic C bug of writing = where == was intended inside a condition.

3.6  Operator Overloading

Operator overloading lets user-defined types participate in operator expressions. Each language exposes it differently: // Rust (implement a trait from std::ops) use std::ops::Add; impl Add for Vec2 { type Output = Vec2; fn add(self, other: Vec2) -> Vec2 { /* ... */ } } // C++ (define a member or free function) Vec2 operator+(const Vec2& a, const Vec2& b) { /* ... */ } // C# (static method with operator keyword) public static Vec2 operator+(Vec2 a, Vec2 b) { /* ... */ } // Python (dunder method) def __add__(self, other): return Vec2(self.x + other.x, self.y + other.y) Rust and C++ allow overloading most operators. C# allows arithmetic, comparison, and conversion operators but not assignment. Python overloads through dunder (double-underscore) methods. None of the four allow overloading && and || in a way that preserves short-circuit semantics.

3.7  Epilogue

Operators express the arithmetic and logical intent of a computation concisely. Knowing where languages diverge - overflow, NaN ordering, short-circuit guarantees - prevents subtle portability bugs. The next chapter shows how operators combine with control flow to direct program execution.

3.8  References

Rust Operator Expressions - Reference
C++ Operator Precedence - cppreference
C# Operators - Microsoft
Python Operator Precedence