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 binary

String 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 — &str is more flexible than &String since both String and 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 thing

Use 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

TypeOwnedMutableUse when
&strnonoreading/passing strings, function params
Stringyesyesbuilding, storing, or returning strings
&Pathnonoreading file paths
PathBufyesyesbuilding or storing file paths
OsStr / OsStringno/yesno/yesOS strings that may not be UTF-8
CStr / CStringno/yesno/yesFFI with C code

When in doubt: use &str for function parameters, String for owned data.