In any programming language, strings are essential. We’ll be hard-pressed to find any real-life application that doesn’t rely on strings. One of the everyday operations on strings is comparison. We can use the eq(), eq_ignore_ascii_case(), and == to compare strings in Rust.
Unlike other programming languages, strings in Rust have peculiar characteristics. Here are things you need to know about strings when learning Rust.
Using eq / ne To Compare String and &str Values
We use these functions to compare strings in Rust. Both of them are case-sensitive. Consider the following code snippets. The first code snippet compares String values. Although these are String values, we still need to pass their references to the other String to the function.
1 2 3 | let apple: String = String::from("apple"); let banana: String = String::from("banana"); println!("{}", apple.eq(&banana)); |
The codes generate the following output.
1 | false |
When it comes to &str values, the codes are similar.
1 2 3 4 | let apple: &str = "apple"; let banana: &str = "banana"; println!("{}", apple.eq(banana)); println!("{}", banana.eq("banana")); |
The codes output the following.
1 2 | false true |
The eq() function comes from the Eq trait. We get these codes when we open the string.rs file that comes with the Rust distribution.
1 2 3 4 5 6 7 8 | ... #[derive(PartialOrd, Eq, Ord)] #[cfg_attr(not(test), rustc_diagnostic_item = "string_type")] #[stable(feature = "rust1", since = "1.0.0")] pub struct String { vec: Vec<u8>, } ... |
The Eq trait extends the PartialEq trait ( cmp.rs). These are the main traits that allow our codes to compare strings in Rust.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ... #[doc(alias = "==")] #[doc(alias = "!=")] #[stable(feature = "rust1", since = "1.0.0")] pub trait Eq: PartialEq<Self> { // this method is used solely by #[deriving] to assert // that every component of a type implements #[deriving] // itself, the current deriving infrastructure means doing this // assertion without using a method on this trait is nearly // impossible. // // This should never be implemented by hand. #[doc(hidden)] #[inline] #[stable(feature = "rust1", since = "1.0.0")] fn assert_receiver_is_total_eq(&self) {} } ... #[lang = "eq"] #[stable(feature = "rust1", since = "1.0.0")] #[doc(alias = "==")] #[doc(alias = "!=")] #[rustc_on_unimplemented( message = "can't compare `{Self}` with `{Rhs}`", label = "no implementation for `{Self} == {Rhs}`" )] pub trait PartialEq<Rhs: ?Sized = Self> { /// This method tests for `self` and `other` values to be equal, and is used /// by `==`. #[must_use] #[stable(feature = "rust1", since = "1.0.0")] fn eq(&self, other: &Rhs) -> bool; /// This method tests for `!=`. #[inline] #[must_use] #[stable(feature = "rust1", since = "1.0.0")] fn ne(&self, other: &Rhs) -> bool { !self.eq(other) } } ... |
Aside from the eq() function, we can also use the ne() (Not Equal) function. We will get a different output if we replace eq() with ne() in the code snippets. Consider the following codes for String values.
1 2 3 4 5 | let apple: String = String::from("apple"); let banana: String = String::from("banana"); // true println!("{}", apple.ne(&banana)); |
Finally, the following are codes for &str references.
1 2 3 4 5 6 7 8 | let apple: &str = "apple"; let banana: &str = "banana"; // true println!("{}", apple.ne(banana)); // false println!("{}", banana.ne("banana")); |
Why do these functions accept references instead of String values? If we dig deeper, we will find the following code snippet in string.rs. These are the codes that compare strings in Rust.
1 2 3 4 5 6 7 8 9 10 11 12 13 | ... #[stable(feature = "rust1", since = "1.0.0")] impl PartialEq for String { #[inline] fn eq(&self, other: &String) -> bool { PartialEq::eq(&self[..], &other[..]) } #[inline] fn ne(&self, other: &String) -> bool { PartialEq::ne(&self[..], &other[..]) } } ... |
If we drill into those highlighted methods, we’d see these codes. The implementations are on immutable string str, which is only accessible via &str.
1 2 3 4 5 6 7 8 9 10 11 12 13 | ... #[stable(feature = "rust1", since = "1.0.0")] impl PartialEq for str { #[inline] fn eq(&self, other: &str) -> bool { self.as_bytes() == other.as_bytes() } #[inline] fn ne(&self, other: &str) -> bool { !(*self).eq(other) } } ... |
For more information, please see the Eq trait and this blog post.
Using The == And != Operators
We can use these operators to compare strings in Rust when we use them on String or str values; they invoke the eq() and ne() methods accordingly. Operands must be of types that extend or implement both the Eq and PartialEq traits for the operators to work. With these operators, we don’t need to “pass” &str as we would with eq() and ne().
1 2 3 4 5 6 7 | let apple: String = String::from("apple"); let banana: String = String::from("banana"); println!("{}", apple.ne(&banana)); println!("{}", apple == banana); // This won't work println!("{}", apple != &banana); |
Using eq_ignore_ascii_case
The eq_ignore_ascii_case() is a function on the str type. It compares strings in Rust without regard to their cases.
1 2 | let apple: String = String::from("apple"); println!("{}", apple.eq_ignore_ascii_case("APple")); |
Output
1 | true |
For more information, please see the notes.
Using cmp For Sort Order
At some point in our codes, we want to know which string value is greater or less than another string value. To achieve that, we could use the cmp() method, which comes from the Ord trait. Consider the following example.
1 2 3 4 5 6 7 8 9 10 11 | fn main() { let apple: &str = "apple"; let banana: &str = "banana"; let order1 = apple.cmp(banana); println!("{:?}", order1); let order2 = banana.cmp(apple); println!("{:?}", order2); } |
The codes generate the following output.
1 2 | Less Greater |
From the sample codes, “apple” comes before “banana” in terms of sort order.
Using partial_cmp For Sort Order
This method comes from the PartialOrd trait. Typically, we would use this trait to compare items based on their attributes. For example, we compare instances of struct Person only by last names. However, for String and &str, the comparison reuses the cmp() method.
1 2 3 4 5 6 7 8 9 | ... #[stable(feature = "rust1", since = "1.0.0")] impl PartialOrd for str { #[inline] fn partial_cmp(&self, other: &str) -> Option<Ordering> { Some(self.cmp(other)) } } ... |
Other Rust String Types
And those are the usual ways to compare strings in Rust! However, there are other string types in Rust – OsString, and CString, and their reference type equivalents OsStr and Cstr, respectively. They are, in most ways, similar to String and &str.