5.5 Operators
Operators in Rust allow you to perform operations on variables and values. Rust provides a wide range of operators, including unary, binary, and assignment operators, similar to C and C++. However, there are some key differences, such as the absence of certain operators like ++
and --
. In this section, we will cover Rust’s operators in detail, explain operator precedence, and compare them to those in C/C++. We will also explore how to define custom operators in Rust.
5.5.1 Unary Operators
Unary operators operate on a single operand. Rust provides the following unary operators:
- Negation (
-
): Negates the value of a number.- Example:
-x
- Example:
- Logical negation (
!
): Inverts the value of a boolean.- Example:
!true
evaluates tofalse
- Example:
- Dereference (
*
): Dereferences a reference to access the underlying value.- Example:
*pointer
- Example:
- Reference (
&
): Creates a reference to a value.- Example:
&x
creates a reference tox
.
- Example:
Example program using unary operators:
fn main() { // editable example let x = 5; let neg_x = -x; let is_false = !true; let reference = &x; let deref_x = *reference; println!("Negation of {} is {}", x, neg_x); println!("The opposite of true is {}", is_false); println!("Reference to x is: {:?}", reference); println!("Dereferenced value is: {}", deref_x); }
5.5.2 Binary Operators
Binary operators in Rust work on two operands. These include arithmetic, logical, comparison, and bitwise operators.
Arithmetic Operators
- Addition (
+
): Adds two values. - Subtraction (
-
): Subtracts the second value from the first. - Multiplication (
*
): Multiplies two values. - Division (
/
): Divides the first value by the second (integer division for integers). - Modulus (
%
): Finds the remainder after division.
Example:
fn main() { let a = 10; let b = 3; let sum = a + b; let difference = a - b; let product = a * b; let quotient = a / b; let remainder = a % b; println!("{} + {} = {}", a, b, sum); println!("{} - {} = {}", a, b, difference); println!("{} * {} = {}", a, b, product); println!("{} / {} = {}", a, b, quotient); println!("{} % {} = {}", a, b, remainder); }
Note that Rust's binary arithmetic operators generally require both operands to have the same type, meaning expressions like 1u8 + 2i32
or 1.0 + 2
are invalid.
Comparison Operators
- Equal to (
==
): Checks if two values are equal. - Not equal to (
!=
): Checks if two values are not equal. - Greater than (
>
): Checks if the first value is greater than the second. - Less than (
<
): Checks if the first value is less than the second. - Greater than or equal to (
>=
): Checks if the first value is greater than or equal to the second. - Less than or equal to (
<=
): Checks if the first value is less than or equal to the second.
These operators work on integers, floating-point numbers, and other comparable types.
Example:
fn main() { let x = 5; let y = 10; println!("x == y: {}", x == y); println!("x != y: {}", x != y); println!("x < y: {}", x < y); println!("x > y: {}", x > y); }
Logical Operators
- Logical AND (
&&
): Returnstrue
if both operands aretrue
. - Logical OR (
||
): Returnstrue
if at least one operand istrue
.
Example:
fn main() { let a = true; let b = false; println!("a && b: {}", a && b); println!("a || b: {}", a || b); }
Bitwise Operators
- Bitwise AND (
&
): Performs a bitwise AND operation. - Bitwise OR (
|
): Performs a bitwise OR operation. - Bitwise XOR (
^
): Performs a bitwise XOR operation. - Left shift (
<<
): Shifts the bits of the left operand to the left by the number of positions specified by the right operand. - Right shift (
>>
): Shifts the bits of the left operand to the right by the number of positions specified by the right operand.
For shift operations, there is a key distinction between signed and unsigned integer types. For unsigned types, right shifts fill the leftmost bits with zeros. For signed types, right shifts use sign extension, meaning that the leftmost bit (the sign bit) is preserved, which maintains the negative or positive sign of the number.
Example:
fn main() { let x: u8 = 2; // 0000_0010 in binary let y: u8 = 3; // 0000_0011 in binary println!("x & y: {}", x & y); // 0000_0010 println!("x | y: {}", x | y); // 0000_0011 println!("x ^ y: {}", x ^ y); // 0000_0001 println!("x << 1: {}", x << 1); // 0000_0100 println!("x >> 1: {}", x >> 1); // 0000_0001 let z: i8 = -2; // 1111_1110 in binary (signed) println!("z >> 1 (signed): {}", z >> 1); // Sign bit is preserved: 1111_1111 }
5.5.3 Assignment Operators
The assignment operator in Rust is the equal sign (=
), which is used to assign values to variables. Rust also supports compound assignment operators, which combine arithmetic or bitwise operations with assignment:
- Add and assign (
+=
):x += 1;
- Subtract and assign (
-=
):x -= 1;
- Multiply and assign (
*=
):x *= 1;
- Divide and assign (
/=
):x /= 1;
- Modulus and assign (
%=
):x %= 1;
- Bitwise AND and assign (
&=
):x &= y;
- Bitwise OR and assign (
|=
):x |= y;
- Bitwise XOR and assign (
^=
):x ^= y;
- Left shift and assign (
<<=
):x <<= y;
- Right shift and assign (
>>=
):x >>= y;
Example:
#![allow(unused)] fn main() { let mut x = 5; x += 2; println!("x after addition: {}", x); }
5.5.4 Ternary Operator
Rust does not have a traditional ternary operator like C's ? :
. Instead, Rust uses if
expressions that can return values, making the ternary operator unnecessary.
Example of an if
expression in Rust:
#![allow(unused)] fn main() { let condition = true; let result = if condition { 5 } else { 10 }; println!("The result is: {}", result); }
5.5.5 Custom Operators and Operator Overloading
Unlike C++, Rust does not allow defining new custom operators (e.g., using special Unicode characters). However, Rust does support operator overloading through traits. You can implement Rust's built-in traits, like Add
, to define custom behavior for existing operators.
Example: Overloading the +
operator for a custom type.
use std::ops::Add; struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 3, y: 4 }; let p3 = p1 + p2; // Uses the overloaded + operator println!("p3: x = {}, y = {}", p3.x, p3.y); }
In this example, the +
operator is overloaded for the Point
struct by implementing the Add
trait. This allows two Point
instances to be added using the +
operator.
5.5.6 Operator Precedence
Operator precedence in Rust determines the order in which operations are evaluated. Rust’s precedence rules are similar to those in C and C++, with multiplication and division taking precedence over addition and subtraction, and parentheses ()
being used to control the order of evaluation.
Here is a simplified operator precedence table (from highest to lowest precedence):
- Method call and field access:
.
- Function call and array indexing:
()
and[]
- Unary operators:
-
,!
,*
,&
- Multiplicative:
*
,/
,%
- Additive:
+
,-
- Bitwise shifts:
<<
,>>
- Bitwise AND:
&
- Bitwise XOR:
^
- Bitwise OR:
|
- Comparison and equality:
==
,!=
,<
,<=
,>
,>=
- Logical AND:
&&
- Logical OR:
||
- Range operators:
..
,..=
- Assignment and compound assignment:
=
,+=
,-=
, etc.
Example:
#![allow(unused)] fn main() { let result = 2 + 3 * 4; println!("Result without parentheses: {}", result); // Outputs 14 let result_with_parentheses = (2 + 3) * 4; println!("Result with parentheses: {}", result_with_parentheses); // Outputs 20 }
5.5.7 Comparison with C and C++
Rust’s operators are quite similar to those in C and C++. However, Rust lacks the ++
and --
operators, which increment or decrement variables in C/C++. This design decision in Rust prevents unintended side effects and encourages clearer code, requiring you to use += 1
or -= 1
explicitly for incrementing or decrementing values.