Rustlers Atom 1.8: Strings
April 18, 2026•493 words
Strings in Rust are collections of UTF-8 bytes, wrapped in safe abstractions.
&str
&str (read “string slice”) is a view into UTF-8 text. It doesn’t own the data — it borrows it. String literals like "hello" are of type &'static str (a slice baked into the program’s binary).
fn main() {
let greeting: &str = "Hello, world!";
println!("{}", greeting);
}
You can think of &str as a window into some existing string data.
Because it borrows, it’s lightweight and fast to copy or pass around. Most APIs that only read text take &str.
fn shout(msg: &str) {
println!("{}", msg.to_uppercase());
}
String
String is an owned, heap-allocated, mutable buffer of UTF-8 bytes.
You create it when you need to build or store text dynamically.
fn main() {
let mut s = String::from("Hello");
s.push_str(", Rust!");
println!("{}", s);
}
Because String owns its memory, when it goes out of scope, it automatically frees that heap allocation — no GC needed, no leaks possible.
Converting Between String and &str
Conversions between String and &str are cheap and safe. Rust ensures lifetimes line up correctly.
let s = String::from("data");
let slice: &str = &s; // Borrow
let slice = "data";
let owned = slice.to_string(); // or String::from(slice)
Slices of Strings
You can borrow slices of strings by byte ranges, but slicing must align with UTF-8 character boundaries.
fn main() {
let s = String::from("Fierro 🦀");
let first_word = &s[0..6]; // "Fierro"
println!("{}", first_word);
}
Rust will panic at runtime if you cut through a multi-byte character. That’s why string iteration is usually done over chars or graphemes, not raw bytes.
When to Use Which
When working with text in Rust, choosing the right type depends on what you want to do with it. If your goal is to handle read-only or static text, such as predefined messages or string literals, the &str type is ideal—it represents a borrowed string slice and doesn’t require heap allocation.
When you need to build, store, or modify text dynamically, use String, which owns its contents and allows mutation.
When designing functions or APIs that should accept either owned or borrowed strings, it’s best to use a flexible parameter type like impl AsRef<str> allowing callers to pass String or &str seamlessly. You can also use a plain &str, but this solution is slightly less flexible — it only works with values that can already be borrowed as a string slice, requiring manual conversion for other string-like types.
fn shout(msg: impl AsRef<str>) {
let text = msg.as_ref();
println!("{}", text.to_uppercase());
}
fn main() {
let static_str = "hello from &str";
let owned_string = String::from("hello from String");
// Works directly with a string slice
shout(static_str);
// Also works with a String, thanks to deref coercion
shout(&owned_string);
}