title: String Types
Rust has multiple string types, which can be confusing at first. The two you’ll use almost exclusively are String and &str.
&str — string slice
A &str is an immutable reference to a sequence of UTF-8 bytes stored somewhere in memory. It does not own the data.
let s: &str = "hello"; // string literal — stored in the binaryString literals are &'static str — they live for the entire duration of the program.
&str can also be a slice of a String:
let s = String::from("hello world");
let hello: &str = &s[0..5];Use &str when:
- You only need to read a string, not modify or own it
- Writing function parameters —
&stris more flexible than&Stringsince bothStringand string literals can be passed as&str
fn greet(name: &str) { // accepts both &str and &String
println!("Hello, {name}!");
}String — owned string
String is a heap-allocated, growable, owned string. Use it when you need to build, modify, or own string data.
let mut s = String::new();
s.push_str("hello");
s.push(' ');
s.push_str("world");
let s = String::from("hello");
let s = "hello".to_string(); // same thingUse String when:
- You need to build or modify a string at runtime
- You need to store a string in a struct or return it from a function
- You need to own the string data
Converting between them
let owned: String = "hello".to_string();
let owned: String = String::from("hello");
let borrowed: &str = &owned; // deref coercion
let borrowed: &str = owned.as_str();String formatting
Build strings with format! — works like println! but returns a String:
let name = "Alice";
let age = 30;
let s = format!("{name} is {age} years old");Concatenation
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2; // s1 is moved here, s2 is borrowed
// s1 is no longer valid
// format! avoids the ownership awkwardness
let s3 = format!("{s1}{s2}");Other string types
These come up less often but are worth knowing:
OsString / OsStr
For interacting with the operating system — file paths, environment variables, command-line arguments. The OS doesn’t guarantee strings are valid UTF-8, so these types handle that.
use std::ffi::OsStr;
let arg: &OsStr = std::env::args_os().next().unwrap().as_os_str();PathBuf / Path
Specifically for file system paths. Prefer these over String for paths — they handle platform differences (e.g. / vs \) correctly.
use std::path::{Path, PathBuf};
let path = PathBuf::from("/home/user/file.txt");
let path: &Path = Path::new("/home/user/file.txt");
path.extension(); // Some("txt")
path.file_name(); // Some("file.txt")
path.parent(); // Some("/home/user")PathBuf is to Path as String is to &str — owned vs borrowed.
CString / CStr
For FFI — interacting with C libraries that expect null-terminated strings.
Summary
| Type | Owned | Mutable | Use when |
|---|---|---|---|
&str | no | no | reading/passing strings, function params |
String | yes | yes | building, storing, or returning strings |
&Path | no | no | reading file paths |
PathBuf | yes | yes | building or storing file paths |
OsStr / OsString | no/yes | no/yes | OS strings that may not be UTF-8 |
CStr / CString | no/yes | no/yes | FFI with C code |
When in doubt: use &str for function parameters, String for owned data.