Two string types
Most languages have one string type. Rust has two that you will meet in your first program, and the difference explains many early compiler errors.
&str(a "string slice") is a borrowed, read-only view of text stored somewhere else. Every string literal such as"Algo"is a&str. You can read it, slice it and print it, but you can't append to it.Stringis an owned, growable buffer. Your code owns the bytes, so it may append to them, clear them, or hand them to another function.
Think of &str as looking at a page in a book and String as having your own notebook that you can write in.
Converting between them
From borrowed to owned, call to_string() on the slice. It copies the text into a new buffer. String::from("Algo"), "Algo".to_owned() and "Algo".into() (when the type is known) do the same:
let mut name: String = "Al".to_string();
name.push_str("go");
let peek: &str = &name[0..2];
println!("{} {}", name, peek);
Algo Al
From owned back to borrowed, put a & in front. A range slice such as &name[0..2] borrows bytes 0 and 1. name.as_str() borrows the whole thing. A &String also converts to &str automatically where a &str is expected, which is why functions normally take &str parameters: they accept literals and borrowed Strings alike.
fn shout(s: &str) -> String {
s.to_uppercase()
}
Calling shout("lit") and shout(&owned) both work. A function that takes a String parameter, on the other hand, takes ownership of the value, so callers have to pass an owned string.
The classic errors
Three mistakes account for most trouble, and each has a clear compiler message.
- A literal where a
Stringis required.let owned: String = "Algo";fails withexpected `String`, found `&str`. The compiler'shelp:line suggests the fix,"Algo".to_string(). The conversion is never implicit, because it allocates memory and Rust keeps that visible. - Growing a
&str."Algo".push_str("Cademy")fails withno method named `push_str` found for reference `&str`. The method exists only onString. - Forgetting
mut. Even on aString,push_strneeds a mutable binding. Without it the compiler sayscannot borrow `owned` as mutable, as it is not declared as mutable.
Making a &str variable mut doesn't change its type. It lets the variable point at a different slice, but the text it views still can't grow.
The lesson's exercise
The exercise makes the round trip from borrowed to owned and back to borrowed. The steps are:
- Bind the literal to
slicewith the explicit type&str. - Convert it into a mutable
Stringnamedownedwithto_string(). It ismutbecause the next step changes it. - Append
"Cademy"withpush_str, which works becauseownedis aString. - Print it with the label
owned =in front. - Borrow a view back into
first_fourwith&owned[0..4]. The&matters, becauseowned[0..4]by itself is a barestrof unknown size and can't be stored in a variable. - Print
first_fourwith its label. A&strprints with{}just like aString. - Print the length of
owned, which is 10.
let slice: &str = "Algo";
let mut owned: String = slice.to_string();
owned.push_str("Cademy");
println!("owned = {}", owned);
let first_four: &str = &owned[0..4];
println!("first_four = {}", first_four);
println!("len = {}", owned.len());
owned = AlgoCademy
first_four = Algo
len = 10
Three Rust details show up here. First, len() counts bytes, which equals the number of characters for ASCII text like this. Second, while first_four is alive it borrows owned, so Rust would reject a change to owned that comes before the last use of first_four. Third, slicing copies nothing: first_four points into the same buffer.
Key takeaways
- A literal is a
&str: borrowed, read-only, fixed. - A
Stringis owned and growable, and it needsmutto be changed. .to_string()turns a slice into aString;&oras_str()borrows a&strback.- Prefer
&strfor function parameters; the compiler'shelp:lines usually show the conversion you need.
