Rust Strings
A String is owned text that can grow and change. A &str is a borrowed view of text.
Owned text can grow
String::from makes text you own. push_str adds text, and push adds one character:
let mut s = String::from("Ferris");
s.push_str(" the crab");
s.push('!');
println!("{s}");Start empty, then build
String::new starts with empty owned text. format! builds a new String from pieces:
let mut word = String::new();
word.push_str("ferris");
let msg = format!("Hello, {}!", word);
println!("{msg}");Two spellings for the same job
"text".to_string() does the same job as String::from("text") — both give you an owned String:
let a = String::from("rust");
let b = "rust".to_string();
println!("{} {}", a == b, b.len());Trim the characters you choose
.trim() cuts spaces. trim_matches with a set cuts any of those characters from both ends:
let s = " ,hello, ";
println!("{}", s.trim_matches(&[' ', ',']));Views out, copies back
as_str hands back the &str view of a String with no copy, and as_bytes shows its bytes. to_owned walks the other way, copying a view into a String you own:
let text = String::from("ferris");
println!("view = {}", text.as_str());
println!("first byte = {}", text.as_bytes()[0]);
let copy = text.as_str().to_owned();
println!("copy = {copy}");Letters are not bytes
é takes two bytes, so this text has 5 letters but 6 bytes — counted with .chars(), never with s[0]:
let s = "héllo";
println!("{} letters, {} bytes", s.chars().count(), s.len());Example
fn main() {
let owned = String::from("Ferris");
let msg = format!("Hello, {}!", owned);
let msg2 = format!("Hello, {}!", "world");
println!("{} / {}", msg, msg2);
println!("chars: {}", "héllo".chars().count());
}Key points
- String: text you own.
- It can grow and change.
- It lives on the heap (the program's big storage area).
- A String is a Vec of bytes (Vec<u8>) with one extra promise: those bytes are always valid UTF-8 text
- A String is not null terminated.
- It stores its length.
- It does not end the text with a special zero marker, as C strings do.
- A String owns its text: a &str only views text owned elsewhere.
In this example, format! builds a greeting from an owned String. owned holds the text "Ferris", and format! uses it to fill the {} slot. A plain literal fills the same slot in the second greeting. Each greeting is a new owned String. The last line counts the characters of "héllo" with .chars().count(): 5 characters. The text needs 6 bytes, because é takes two bytes. That is exactly why Rust does not let you index text by position.
Note
In many languages there is only one string type.
In Rust, functions that only read text should ask for &str — then they accept both owned text and plain literals.
Another difference: you cannot read a character with s[0], because letters can take more than one byte.
Use .chars() instead.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A function only reads text and never changes it. Which parameter type is the usual Rust choice?