Comparison Operators
Comparison operators compare two values and produce a bool. Rust has six of them: two test equality and four test ordering.
| Operator | Meaning | Trait | Method called |
|---|---|---|---|
== |
Equal to | PartialEq |
eq |
!= |
Not equal to | PartialEq |
ne |
< |
Less than | PartialOrd |
lt |
> |
Greater than | PartialOrd |
gt |
<= |
Less than or equal to | PartialOrd |
le |
>= |
Greater than or equal to | PartialOrd |
ge |
fn main() {
let a = 5;
let b = 8;
println!("a == b: {}", a == b);
println!("a != b: {}", a != b);
println!("a < b: {}", a < b);
println!("a > b: {}", a > b);
println!("a <= b: {}", a <= b);
println!("a >= b: {}", a >= b);
}
a == b: false
a != b: true
a < b: true
a > b: false
a <= b: true
a >= b: false
A comparison always evaluates to bool, which makes it the natural condition for if, while, and match guards.
How Comparisons Work
a == b is shorthand for PartialEq::eq(&a, &b), and a < b is shorthand for PartialOrd::lt(&a, &b). The operators take their operands by reference, so a comparison never moves or consumes a value:
fn main() {
let first = String::from("rust");
let second = String::from("rust");
println!("{}", first == second);
println!("{first} {second}");
}
true
rust rust
Both first and second are still usable after the comparison.
A type supports a comparison operator when it implements the matching trait. All primitive types, String, &str, tuples, arrays, slices, Vec<T>, Option<T>, and many other standard types already do.
Both Operands Must Have a Comparable Type
For primitive numbers, both sides must have the same type. Rust does not convert between numeric types in a comparison.
// ❌ does not compile
fn main() {
let a: i32 = 5;
let b: i64 = 5;
println!("{}", a == b);
}
error[E0308]: mismatched types
--> e1.rs:5:25
|
5 | println!("{}", a == b);
| - ^ expected `i32`, found `i64`
| |
| expected because this is `i32`
|
help: you can convert `a` from `i32` to `i64`, matching the type of `b`
|
5 | println!("{}", i64::from(a) == b);
| ++++++++++ +
Convert one side explicitly. Use From when the conversion cannot lose information, TryFrom when it can, and as when truncation is acceptable:
fn main() {
let small: i32 = 3;
let big: i64 = 5;
println!("{}", i64::from(small) < big);
println!("{}", u8::try_from(300i32).is_err());
}
true
true
Comparing unrelated types fails because no implementation exists:
// ❌ does not compile
fn main() {
let x = 3;
println!("{}", x == "3");
}
error[E0277]: can't compare `{integer}` with `&str`
--> e9.rs:4:22
|
4 | println!("{}", x == "3");
| ^^ no implementation for `{integer} == &str`
|
= help: the trait `PartialEq<&str>` is not implemented for `{integer}`
References Versus Values
&i32 and i32 are different types, so they cannot be compared directly. This comes up often with iterators, which yield references:
// ❌ does not compile
fn main() {
let numbers = vec![3, 1, 2];
let first = numbers.iter().next().unwrap();
println!("{}", first == 3);
}
error[E0277]: can't compare `&{integer}` with `{integer}`
--> e4.rs:5:26
|
5 | println!("{}", first == 3);
| ^^ no implementation for `&{integer} == {integer}`
|
= help: the trait `PartialEq<{integer}>` is not implemented for `&{integer}`
help: consider dereferencing here
|
5 | println!("{}", *first == 3);
| +
Dereference the reference (*first == 3) or compare two references (first == &3). When both sides are references of the same level, the comparison goes through to the values they point to:
fn main() {
let x = 10;
let y = 10;
println!("{}", &x == &y);
println!("{}", &&x == &&y);
println!("{}", std::ptr::eq(&x, &y));
}
true
true
false
&x == &y compares the values 10 and 10, not the addresses. To compare addresses, use std::ptr::eq.
Equality: PartialEq and Eq
PartialEq provides == and !=. != is defined as the negation of ==, so a type only needs to implement eq.
PartialEq is partial because equality is not guaranteed to be reflexive for every type. The Eq trait is a marker that adds the guarantee that a == a is always true. Every integer, bool, char, and string type implements Eq. f32 and f64 do not.
Deriving Equality
A struct or enum has no == until it opts in. The compiler rejects the comparison otherwise:
// ❌ does not compile
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
let q = Point { x: 1, y: 2 };
println!("{}", p == q);
}
error[E0369]: binary operation `==` cannot be applied to type `Point`
--> e3.rs:10:22
|
10 | println!("{}", p == q);
| - ^^ - Point
| |
| Point
|
note: an implementation of `PartialEq` might be missing for `Point`
--> e3.rs:2:1
|
2 | struct Point {
| ^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `Point` with `#[derive(PartialEq)]`
|
2 + #[derive(PartialEq)]
3 | struct Point {
|
#[derive(PartialEq)] compares all fields in order, and two values are equal when every field is equal:
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
println!("{}", Point { x: 1, y: 2 } == Point { x: 1, y: 2 });
println!("{}", Point { x: 1, y: 2 } != Point { x: 1, y: 3 });
}
true
true
Implementing Equality by Hand
A manual implementation defines which fields count. Here two users are equal when their id matches, regardless of name:
#[derive(Debug, Eq)]
struct User {
id: u32,
name: String,
}
impl PartialEq for User {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
fn main() {
let first = User { id: 1, name: "Ann".into() };
let second = User { id: 1, name: "Anna".into() };
println!("{}", first == second);
println!("{} vs {}", first.name, second.name);
}
true
Ann vs Anna
Comparing Different Types
PartialEq<Rhs> takes a type parameter that defaults to Self. Implementing it with another type allows a comparison across types. The standard library does this for String and &str, for Vec<T> and arrays, and in other places:
fn main() {
let owned = String::from("hello");
println!("{}", owned == "hello");
println!("{}", "hello" == owned);
println!("{}", owned.as_str() == "hello");
println!("{}", vec![1, 2] == [1, 2]);
println!("{}", [1, 2, 3][..2] == [1, 2]);
}
true
true
true
true
true
A user-defined cross-type comparison:
#[derive(Debug, PartialEq)]
struct Meters(f64);
#[derive(Debug, PartialEq)]
struct Feet(f64);
impl PartialEq<Feet> for Meters {
fn eq(&self, other: &Feet) -> bool {
(self.0 - other.0 * 0.3048).abs() < 1e-9
}
}
fn main() {
println!("{}", Meters(0.3048) == Feet(1.0));
println!("{}", Meters(1.0) == Meters(1.0));
}
true
true
Feet(1.0) == Meters(0.3048) would not compile, because only Meters: PartialEq<Feet> exists.
Ordering: PartialOrd and Ord
PartialOrd provides <, >, <=, and >=. Its one required method is partial_cmp, which returns Option<Ordering>. Ordering is an enum with three variants:
use std::cmp::Ordering;
fn main() {
println!("{:?} {:?} {:?}", 1.cmp(&2), 2.cmp(&2), 3.cmp(&2));
println!("{:?}", 1.5f64.partial_cmp(&2.5));
let a = 5;
let b = 8;
match a.cmp(&b) {
Ordering::Less => println!("less"),
Ordering::Equal => println!("equal"),
Ordering::Greater => println!("greater"),
}
}
Less Equal Greater
Some(Less)
less
| Trait | Method | Returns | Guarantees |
|---|---|---|---|
PartialOrd |
partial_cmp |
Option<Ordering> |
Some pairs of values may have no order (None) |
Ord |
cmp |
Ordering |
Every pair of values has an order (a total order) |
Ord requires Eq and PartialOrd. A type that implements Ord can be sorted with sort(), used as a BTreeMap key, and passed to max() and min() on an iterator.
Ordering has helper methods:
use std::cmp::Ordering;
fn main() {
println!("{:?}", 1.cmp(&2).reverse());
println!("{:?}", 1.cmp(&1).then(Ordering::Greater));
println!("{:?}", (1, 2).cmp(&(1, 3)));
println!("{} {} {}", 3.max(7), 3.min(7), 15.clamp(0, 10));
}
Greater
Greater
Less
7 3 10
then returns the first ordering unless it is Equal, in which case it returns the second. It chains comparisons by priority.
Ordering Rules for Built-in Types
| Type | Ordering |
|---|---|
| Integers, floats | Numeric order |
bool |
false < true |
char |
By Unicode code point |
&str, String |
Lexicographic, byte by byte (UTF-8) |
| Tuples | Lexicographic: first elements, then second elements if equal, and so on |
Arrays, slices, Vec<T> |
Lexicographic by elements, then a shorter sequence is less than a longer one with the same prefix |
Option<T> |
None is less than any Some |
Result<T, E> |
Any Ok is less than any Err |
() |
Always equal |
fn main() {
println!("bool: {}", false < true);
println!("char: {}", 'a' < 'b');
println!("char: {}", 'Z' < 'a');
println!("tuple: {}", (1, "z") < (2, "a"));
println!("tuple: {}", (1, 5) < (1, 7));
println!("array: {}", [1, 2, 3] < [1, 2, 4]);
println!("vec: {}", vec![1, 2] < vec![1, 2, 0]);
println!("vec: {}", vec![3] > vec![1, 2, 3]);
println!("option: {}", None < Some(0));
println!("option: {}", Some(3) > Some(2));
println!("unit: {}", () == ());
}
bool: true
char: true
char: true
tuple: true
tuple: true
array: true
vec: true
vec: true
option: true
option: true
unit: true
'Z' < 'a' is true because 'Z' is code point 90 and 'a' is 97.
Strings Compare by Bytes
String comparison is lexicographic: it compares the first bytes, then the next, and so on. The first difference decides. If one string is a prefix of the other, the shorter string is smaller. The order is not alphabetical for human languages, and it is case-sensitive.
fn main() {
println!("{}", "apple" < "banana");
println!("{}", "Zebra" < "apple");
println!("{}", "abc" < "abcd");
println!("{}", "10" < "9");
println!("{}", "é" > "z");
}
true
true
true
true
true
"Zebra" < "apple"because'Z'(90) is less than'a'(97)."10" < "9"because'1'is less than'9'. Strings that hold numbers are compared as text."é" > "z"becauseéis encoded in UTF-8 as bytes starting with0xC3, which is larger than the byte forz.
For case-insensitive equality, normalize the strings or use a dedicated method:
fn main() {
println!("{}", "apple".to_lowercase() == "APPLE".to_lowercase());
println!("{}", "Hello".eq_ignore_ascii_case("hELLO"));
}
true
true
Deriving Ordering
#[derive(PartialOrd)] orders values lexicographically by field, in declaration order. For enums, variants are ordered by declaration order first, and then by their fields. Deriving PartialOrd also requires PartialEq, and deriving Ord requires Eq and PartialOrd.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Level {
Low,
Medium,
High,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Task {
priority: u8,
name: &'static str,
}
fn main() {
println!("{}", Level::Low < Level::High);
println!("{:?}", Level::Medium.cmp(&Level::High));
let write = Task { priority: 2, name: "write" };
let build = Task { priority: 2, name: "build" };
let idle = Task { priority: 1, name: "idle" };
println!("{} {}", write > build, idle < build);
let mut tasks = vec![write, build, idle];
tasks.sort();
println!("{tasks:?}");
}
true
Less
true true
[Task { priority: 1, name: "idle" }, Task { priority: 2, name: "build" }, Task { priority: 2, name: "write" }]
priority is compared first. name only matters when the priorities are equal. Reordering the fields in the struct definition changes the ordering.
Without a derive, ordering operators are rejected:
// ❌ does not compile
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
let q = Point { x: 3, y: 4 };
println!("{}", p < q);
}
error[E0369]: binary operation `<` cannot be applied to type `Point`
--> e6.rs:11:22
|
11 | println!("{}", p < q);
| - ^ - Point
| |
| Point
|
note: an implementation of `PartialOrd` might be missing for `Point`
--> e6.rs:3:1
|
3 | struct Point {
| ^^^^^^^^^^^^ must implement `PartialOrd`
help: consider annotating `Point` with `#[derive(PartialEq, PartialOrd)]`
|
3 + #[derive(PartialEq, PartialOrd)]
4 | struct Point {
|
Implementing Ordering by Hand
When the derived field order is not the desired order, implement Ord and have PartialOrd delegate to it. Here people are ordered by age, then by name:
use std::cmp::Ordering;
#[derive(Debug, PartialEq, Eq)]
struct Person {
name: String,
age: u32,
}
impl PartialOrd for Person {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Person {
fn cmp(&self, other: &Self) -> Ordering {
self.age
.cmp(&other.age)
.then_with(|| self.name.cmp(&other.name))
}
}
fn main() {
let alice = Person { name: "Alice".into(), age: 30 };
let bob = Person { name: "Bob".into(), age: 25 };
println!("{}", alice > bob);
println!("{:?}", [&alice, &bob].iter().max());
}
true
Some(Person { name: "Alice", age: 30 })
The == and < implementations must agree: if cmp returns Equal, then == must return true.
Floating-Point Comparisons
f32 and f64 implement PartialEq and PartialOrd but not Eq or Ord, because of NaN. Every comparison involving NaN is false, except !=, which is true:
fn main() {
let nan = f64::NAN;
println!("{} {} {} {}", nan == nan, nan != nan, nan < 1.0, nan > 1.0);
println!("{:?}", f64::NAN.partial_cmp(&1.0));
println!("{}", 0.0 == -0.0);
}
false true false false
None
true
NaN is neither less than, equal to, nor greater than any value, so partial_cmp returns None. Positive and negative zero compare equal. The compiler warns about a direct x == f64::NAN, because it is always false. Use is_nan() instead.
Equality of Computed Floats
Floating-point arithmetic rounds, so computed results rarely compare equal to exact constants:
fn main() {
println!("{}", 0.1 + 0.2 == 0.3);
println!("{}", ((0.1 + 0.2) - 0.3f64).abs() < f64::EPSILON);
}
false
true
The usual approach is to test whether the difference is within a tolerance. f64::EPSILON is the gap between 1.0 and the next representable number, so it is a reasonable tolerance only for values near 1.0. Choose a tolerance that matches the magnitude of the values.
Sorting Floats
Because floats have no Ord, sort() does not work on a Vec<f64>:
// ❌ does not compile
fn main() {
let mut values = vec![2.5, 1.0, 3.7];
values.sort();
println!("{values:?}");
}
error[E0277]: the trait bound `{float}: Ord` is not satisfied
--> e7.rs:4:12
|
4 | values.sort();
| ^^^^ the trait `Ord` is not implemented for `{float}`
|
= help: the following other types implement trait `Ord`:
i128
i16
i32
i64
i8
isize
u128
u16
and 4 others
There are two ways to sort floats. partial_cmp with unwrap panics if a NaN is present. total_cmp defines an order for every value, including NaN:
fn main() {
let mut values: Vec<f64> = vec![2.5, -1.0, 3.7, 0.0];
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
println!("{values:?}");
values.sort_by(|a, b| b.total_cmp(a));
println!("{values:?}");
}
[-1.0, 0.0, 2.5, 3.7]
[3.7, 2.5, 0.0, -1.0]
The second sort uses b.total_cmp(a), which reverses the order to descending.
The same limitation applies to max() and min() on iterators of floats. Use f64::max, fold, or max_by(|a, b| a.total_cmp(b)).
Comparison Operators Cannot Be Chained
In some languages a < b < c means "b is between a and c". In Rust it is a syntax error, because comparison operators are non-associative:
// ❌ does not compile
fn main() {
let a = 1;
let b = 2;
let c = 3;
println!("{}", a < b < c);
}
error: comparison operators cannot be chained
--> e2.rs:6:22
|
6 | println!("{}", a < b < c);
| ^ ^
|
= help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
= help: or use `(...)` if you meant to specify fn arguments
help: split the comparison into two
|
6 | println!("{}", a < b && b < c);
| ++++
Combine two comparisons with &&, or use a range with contains:
fn main() {
let (a, b, c) = (1, 2, 3);
println!("{}", a < b && b < c);
println!("{}", (a + 1..c).contains(&b));
println!("{}", (1..=10).contains(&b));
}
true
true
true
== Is Not Assignment
= assigns and == compares. Writing = where a condition is expected produces a type error, because an assignment has type () and not bool:
// ❌ does not compile
fn main() {
let x = 5;
if x = 5 {
println!("five");
}
}
error[E0308]: mismatched types
--> e5.rs:4:8
|
4 | if x = 5 {
| ^^^^^ expected `bool`, found `()`
|
help: you might have meant to compare for equality
|
4 | if x == 5 {
| +
Comparing String With Other Types
String can be compared with &str and str, but not with numbers or other unrelated types:
// ❌ does not compile
fn main() {
let name = String::from("x");
println!("{}", name == 5);
}
error[E0277]: can't compare `String` with `{integer}`
--> e8.rs:4:25
|
4 | println!("{}", name == 5);
| ^^ no implementation for `String == {integer}`
|
= help: the trait `PartialEq<{integer}>` is not implemented for `String`
= help: `String` implements trait `PartialEq<Rhs>`:
PartialEq<&str>
PartialEq<ByteStr>
PartialEq<ByteString>
PartialEq<Cow<'_, str>>
PartialEq<Path>
PartialEq<PathBuf>
PartialEq<str>
PartialEq
Precedence
Comparison operators bind more loosely than arithmetic and more tightly than && and ||. Arithmetic is evaluated first:
fn main() {
let (a, b) = (3, 4);
println!("{}", a + 1 == b);
println!("{}", a * 2 > b && b != 0);
}
true
true
a + 1 == b is (a + 1) == b, and the second line is ((a * 2) > b) && (b != 0).