99% of the time, we deal with strings, which are a sequence of characters, and here are the things you need to know about them when learning Rust.
1. There Are 2 Types of String in Rust: String and str
Rust has essentially two types of string – String and str. A String is a growable, mutable, owned, and UTF-8 encoded string type, while str, which is only accessible via &str type, is a pointer to a String somewhere in the stack, heap, or in binary (e.g., literal string values). Using str is the preferred way to pass a read-only string around.
1 2 3 4 5 6 7 8 | // String from the stack let domain_name: String = String::from("Turreta.com"); // Reference domain_name. At this point we only borrow the value of domain_name let borrowed_domain_name: &str = &domain_name; println!("Borrowed Type's value: {}", borrowed_domain_name); println!("Owned Type's value: {}", domain_name); |
If we used String instead of &str, we would get a compile-time error.
1 2 3 4 5 6 7 8 9 10 | // String from the stack let domain_name: String = String::from("Turreta.com"); // Move ownership of value from domain_name to borrowed_domain_name let borrowed_domain_name: String = domain_name; println!("Borrowed Type's value: {}", borrowed_domain_name); // Compile-time error here! println!("Owned Type's value: {}", domain_name); |
The actual compile-time error is as follows.
1 | error[E0382]: borrow of moved value: `domain_name` |
2. Ways You Can Create Rust Strings from str
One, we can create an empty string using String::new().
1 2 | let mut name = String::new(); println!("{}", name); |
Second, we can convert a string literal to a String using to_string and from functions.
1 2 | let mut name = "turreta.com".to_string(); println!("{}", name); |
1 2 | let mut name = String::from("turreta.com"); println!("{}", name); |
Note that other functions can convert str to String.
3. Rust Strings are UTF-8 encoded
Not all programming languages have UTF-8 encoded strings. Java strings, for instance, are UTF-16 encoded, while Rust strings are in UTF-8.
1 2 3 4 5 6 7 8 | let hello_word = String::from("مرحبا بالعالم!"); println!("{}", hello_word); let hello_word = String::from("Γειά σου Κόσμε!"); println!("{}", hello_word); let hello_word = String::from("你好,世界!"); println!("{}", hello_word); |
These codes output these:
1 2 3 | مرحبا بالعالم! Γειά σου Κόσμε! 你好,世界! |
4. Ways You Can Update Rust Strings
We can use the push_str function to append a string to an existing string.
1 2 3 4 5 | let mut name = String::from("turreta.com"); name.push_str(" loves Rust!"); // Outputs: turreta.com loves Rust! println!("{}", name); |
Another related function, called a push, appends a character to a string.
1 2 3 4 5 6 | let mut name = String::from("turreta.com"); name.push_str(" loves Rust!"); name.push('!'); // Outputs: turreta.com loves Rust!! println!("{}", name); |
5. Ways You Can Concatenate Rust Strings
When concatenating two strings, the second string must reference a string.
1 2 3 4 | let mut name_domain = String::from("turreta.com"); let mut name_state = String::from(" loves Rust!"); let s = name_domain + &name_state; println!("{}", s); |
Why did we use &name_state? If we check out Rust string.rs file, we will see this:
1 2 3 4 5 6 7 8 9 10 | #[stable(feature = "rust1", since = "1.0.0")] impl Add<&str> for String { type Output = String; #[inline] fn add(mut self, other: &str) -> String { self.push_str(other); self } } |
Another way to concatenate strings is by using the format! macro.
1 2 3 4 | let mut name_domain = String::from("turreta.com"); let mut name_state = String::from(" loves Rust!"); let s = format!("{}{}", name_domain, name_state); println!("{}", s); |
6. Rust Does Not Support Indexing; use String Slicing Instead
First, the usual syntax found in other languages for string indexing does not work in Rust.
1 2 3 | let name_domain = String::from("turreta.com"); let first_char = name_domain[0]; println!("{}", first_char); |
These codes will not compile.
1 2 3 4 5 6 7 | error[E0277]: the type `std::string::String` cannot be indexed by `{integer}` --> src\main.rs:3:22 | 3 | let first_char = name_domain[0]; | ^^^^^^^^^^^^^^ `std::string::String` cannot be indexed by `{integer}` | = help: the trait `std::ops::Index<{integer}>` is not implemented for `std::string::String` |
Since strings are UTF-8 encoded and can contain non-English characters, consider these codes.
1 2 3 4 5 | // We can see 3 characters let sentence = String::from("我爱你"); // But the string length is 9 println!("{}", sentence.len()); |
We would expect to see three characters, but internally Rust considers the string to be having a length of 9. As a result, Rust puts restrictions and disallows string indexing. To make the codes work, we need to use string slices.
1 2 3 4 5 | let name_domain = String::from("turreta.com"); let first_char = &name_domain[0..1]; // Ouputs: t println!("{}", first_char); |
If that is the case, how to loop through a String if we need to? We can use the chars() function.
1 2 3 4 5 | let domain_name = String::from("turreta.com"); for letter in domain_name.chars() { println!("{}", letter); } |
The codes print out:
1 2 3 4 5 6 7 8 9 10 11 | t u r r e t a . c o m |
7. Compare Rust Strings using !=, ==, =>, <=, <, and > operators
Strings in Rust implement the PartialEq trait.
1 2 3 4 5 6 7 8 9 10 11 12 | let a = String::from("a"); let b = String::from("b"); if a > b { println!("a is greater than b"); } else if a == b { println!("a is b"); } else { println!("b is greater than a"); } // These codes output: b is greater than a |
You need to know these things about strings to make your learning of Rust easier.
Tested with Rust 1.40.0.