Strings

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:

    // from Chapter 4/code/strings.rs 
    let magician1 = "Merlin"; 
    let greeting = "Hello, world!"; 
    let magician2: &str = "Gandalf"; 
    let magician2: &'static str = "Gandalf"; 
    println!("Magician {} greets magician {} with {}", 
    magician1, magician2, greeting);
    Magician Merlin greets magician Gandalf with Hello, world!  
    let mut str1 = String::new();
    let mut str2 = String::with_capacity(25); 
    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:

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