Text handling is where newcomers trip most, because Rust strings are always valid UTF-8 and never indexed by position.
Rust has two main string types. `String` is an owned, growable, heap-allocated buffer. `&str` is a borrowed slice of UTF-8 text, from a String or a literal. Use &str for parameters, String when you need to own or build text. Convert with s.as_str() / &s one way and s.to_string(), String::from(s) or s.to_owned() the other.
Building text: push_str, push (one char), + (which takes the left String by value: s1 + &s2), and the format! macro, which is usually the clearest. write! into a String works too via std::fmt::Write.
Both types are guaranteed valid UTF-8. A character may take 1 to 4 bytes, so:
s.len()is the length in bytes, not characters.s[0]does not compile. There is no O(1) way to get the n-th character.s.chars()iterates Unicode scalar values,s.bytes()iterates bytes, ands.char_indices()gives valid byte offsets for slicing.
Common operations: trim, split, split_whitespace, lines, contains, starts_with, strip_prefix, replace, to_lowercase, parse::<T>(), repeat, and chars().rev().collect::<String>() to reverse.
What users perceive as a character (a grapheme, e.g. an emoji with a skin-tone modifier) can be several chars; for that you need the unicode-segmentation crate.
Other string types exist for good reasons: OsString/OsStr for OS data that may not be UTF-8, PathBuf/Path for file paths, and CString/CStr for C interop. Do not force those through String.
Example
use std::fmt::Write;
fn initials(full_name: &str) -> String {
full_name.split_whitespace().filter_map(|w| w.chars().next()).collect()
}
fn main() {
let mut s = String::from("héllo");
s.push_str(", wörld");
s.push('!');
println!("{} bytes, {} chars", s.len(), s.chars().count()); // 15 bytes, 13 chars
let greeting = format!("{} ({})", s.to_uppercase(), initials("Ada King Lovelace"));
println!("{greeting}"); // HÉLLO, WÖRLD! (AKL)
let mut report = String::new();
for (i, line) in "a=1\nb=2".lines().enumerate() {
writeln!(report, "{i}: {line}").unwrap();
}
print!("{report}"); // 0: a=1 / 1: b=2
let n: i64 = " 42 ".trim().parse().expect("a number");
println!("{n} {:?}", s.chars().next()); // 42 Some('h') (s[0] would not compile)
}Remember
Stringowns text,&strborrows it; take&strparameterslen()counts bytes; iterate withchars()and slice atchar_indicesoffsetsformat!is the clearest way to build strings
Watch out for
- Assuming
s.len()is the number of characters for non-ASCII text.