The way Rust works with strings differs a bit from strings in other languages. All strings are valid sequences of Unicode (UTF8) bytes. They can contain null bytes, but they are not null terminated as in C.
Rust distinguishes two types of string:
- The strings we have used until now are string slices, whose type is &str. The & points out that a string slice is a reference to a string. They are immutable and have a fixed size. For example, the following bindings declare string slices:
// from Chapter 4/code/strings.rs let magician1 = "Merlin"; let greeting = "Hello, world!";
- Or if we explicitly annotate the string variable with its type:
let magician2: &str = "Gandalf";
- We can also define it as a string literal:
let magician2: &'static str = "Gandalf";
- The &'static denotes that the string is statically allocated, and stored directly in the executable program. When declaring global constants, indicating the type is mandatory, but for a let binding it is optional because the compiler infers the type.
println!("Magician {} greets magician {} with {}", magician1, magician2, greeting);
- This gives the following output:
Magician Merlin greets magician Gandalf with Hello, world!
- Literal strings live as long as the program; they have the lifetime of the program, which is the static lifetime. They are defined in the std::str module.
- A String on the other hand can grow dynamically in size (in fact it is a buffer), and so it must be allocated on the heap. We can create an empty string with:
let mut str1 = String::new();
- Each time the string grows it has to be reallocated in memory. So if you know it will start out as 25 bytes for example, you can create the string with this amount of memory allocated as:
let mut str2 = String::with_capacity(25);
-
As long as the size of str2 is less than 25, there is no reallocation when adding to it. This type is described in the module std::string.
- To convert a string slice into a string, use the method to_string:
let mut str3 = magician1.to_string();
The method to_string() can be used to convert any object to a string (more precisely; any object that implements the ToString trait; we will talk about traits in the next chapter) and this method needs to allocate memory on the heap.
If str3 is a string, then you can make a string slice from it with &str3:
let sl1 = &str3;
A string slice created this way can be considered as a view into the string. It is a reference to the interior of the string, and making it has no cost.
This way is preferable to to_string() when comparing strings, because using & doesn't consume resources, while the function to_string() allocates heap memory:
if &str3 == magician1 { println!("We got the same magician alright!") }
String slices can also be created with a range notation like &str3[1..3]. In fact &str3[..] is the same as &str3.
To build up a string, we can use a number of methods:
- push: To append a character to the string
- push_str: To append another string to the string
You can see them in action in this code snippet:
let c1 = 'Θ'; // character c1 str1.push(c1); println!("{}", str1); // Θ str1.push_str(" Level 1 is finished - "); println!("{}", str1); // Θ Level 1 is finished - str1.push_str("Rise up to Level 2"); println!("{}", str1); // Θ Level 1 is finished - Rise up to Level 2
If you need to get the characters of a string one by one and in order, use the method chars(). This method returns an Iterator, so we can use the for in loop (see the section Looping in Chapter 2, Using Variables and Types):
for c in magician1.chars() { print!("{} - ", c); }
This prints the output as:
M - e - r - l - i - n -
To loop over the parts of a string separated by whitespace, we can use the method split(), which also returns an Iterator:
for word in str1.split(" ") { print!("{} / ", word); }
This prints out the following output:
Θ / Level / 1 / is / finished / - / Rise / up / to / Level / 2 /
To change the first part of a string that matches with another string, use the replace:
let str5 = str1.replace("Level", "Floor");
This code allocates new heap memory for the modified string str5.
It prints out the following output:
Floor 1 is finished - Rise up to Floor 2
Strings can be concatenated with the + operator, but the second argument needs to be of the type string slice &str:
let st1 = "United ".to_string(); // st1 is of type String let st2 = "States"; let country = st1 + st2; println!("The country is {}", country); let st3 = "United ".to_string(); let st4 = "States".to_string(); let country = st3 + &st4; println!("The country is {}", country);
This gives the following output:
The country is United States The country is United States
When writing a function that takes a string as argument, always declare it as a string slice, which is a view into the string, like this:
fn how_long(s: &str) -> usize { s.len() }
The reason is that passing a string str1 as an argument allocates memory, and passing it as a slice does not allocate. The easiest way to do this is the following:
println!("Length of Merlin: {}", how_long(magician1)); println!("Length of str1: {}", how_long(&str1));
You can also pass a part of the string with the range notation, or you can even do the following:
println!("Length of str1: {}", how_long(&str1[3..4]));
The preceding statements print out the following output:
Length of Merlin: 6 Length of str1: 43 Length of str1: 4
Raw strings are prefixed with r or r#. They are printed out literally - new lines or escaped characters are not interpreted, like this:
println!("{}", "Ru\nst"); println!("{}", r"Ru\nst"); println!("{}", r#"Ru\nst"#);
This prints out the following output:
Ru\nst Ru\nst Ru\nst
To convert numbers into strings, use the method from_str, illustrated here:
println!("{:?}", f64::from_str("3.6")); let number: f64 = f64::from_str("3.6").unwrap();
The from_str method also needs the trait FromStr; to use it, you must import the trait with use, as follows:
use std::str::FromStr;
Consult the docs (http://doc.rust-lang.org/std/str/ and http://doc.rust-lang.org/std/string/) for more functionality.
Here is a schema to better see the difference between the two string types:
String |
String slice (&str) |
Mutable: heap memory allocation Module: std::string |
Fixed size: view on string reference(&) Module: std::str |