String Slices vs Owned Strings
You've been using String quite a bit โ that's the owned, heap-allocated string type. But there's another type called &str, which is a string slice. It's just a reference to a contiguous sequence of UTF-8 bytes owned by something else. Think of String as a notebook you own, and &str as a bookmark pointing to a page in that notebook.
fn main() {
let owned: String = String::from("Hello, Rust!");
let slice: &str = &owned; // &str slices the whole string
let partial: &str = &owned[0..5]; // "Hello"
println!("slice: {}", slice);
println!("partial: {}", partial);
}
Try it Yourself ->
Slicing Syntax
Slicing uses the [start..end] syntax where start is inclusive and end is exclusive. You can omit the start for 0, or omit the end for "to the very end." This makes slicing super flexible and readable.
fn main() {
let text = String::from("Hello, World!");
let hello = &text[0..5]; // "Hello"
let world = &text[7..12]; // "World"
let full = &text[..]; // entire string
let start = &text[7..]; // "World!"
println!("{}", hello);
println!("{}", world);
println!("{}", full);
println!("{}", start);
}
Try it Yourself ->
Array Slices
Slices aren't just for strings โ they work with arrays and vectors too. An &[i32] is a borrowed slice of integers, which means you can pass any array or vector to a function that takes a slice, without worrying about the exact size.
fn main() {
let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[1..4]; // [2, 3, 4]
println!("slice: {:?}", slice);
println!("sum: {}", sum_slice(&numbers[..]));
}
fn sum_slice(slice: &[i32]) -> i32 {
slice.iter().sum()
}
Try it Yourself ->
String Literals Are Slices
Here's something cool: every string literal like "hello" is actually a &str that's embedded right into your compiled binary. That's why string literals are immutable โ they're just slices pointing to data baked into your program.
fn main() {
let literal: &str = "I am a string literal";
println!("{}", literal);
}
Try it Yourself ->