&[&str] vs [&str]: passing slices
Published 17 October 2026
What &[&str] means¶
&[&str] is a borrowed slice whose elements are &str. It’s the usual way to accept
“some string slices, any number of them”:
impl RoomBookingSystem {
fn new(room_ids: &[&str]) -> Self {
Self {
room_ids: room_ids.iter().map(|id| id.to_string()).collect(),
meetings: Vec::new(),
}
}
}
RoomBookingSystem::new(&["A101", "B202"]);
["A101", "B202"] is an array with a known length ([&str; 2]). The & borrows it, and
it coerces to &[&str], so the same function takes arrays of any length, Vecs
(&v), and sub-slices (&v[1..]).
Why not [&str]?¶
[&str] on its own is the slice itself, with no pointer. Its length isn’t part of the
type, so it has no size known at compile time (it’s a dynamically sized type, like str
and dyn Trait). Rust can’t pass it by value or store it directly in a struct field:
fn new(room_ids: [&str]) -> Self // error[E0277]: the size for values of type `[&str]`
// cannot be known at compilation time
A DST always lives behind a pointer: &[&str], &mut [&str], Box<[&str]>, Rc<[&str]>.
If you want to drop the &¶
Take a fixed-size array, generic over its length with a const generic:
fn new<const N: usize>(room_ids: [&str; N]) -> Self { /* ... */ }
RoomBookingSystem::new(["A101", "B202"]); // array passed by value
This works, but it only takes arrays, not Vecs or sub-slices. &[&str] is the more
flexible signature.
Owning the collection¶
To pass the elements by value, use an owned container instead of a bare slice:
fn take_vec(room_ids: Vec<&str>) { /* owns the vector */ }
fn take_box(room_ids: Box<[&str]>) { /* owns the boxed slice */ }
These own the collection, but each &str still borrows its text from somewhere else.
To own the text too, use Vec<String> or Box<[String]>.
| Type | Owns the list? | Owns the text? |
|---|---|---|
&[&str] |
no | no |
Vec<&str> |
yes | no |
Box<[&str]> |
yes | no |
Vec<String> |
yes | yes |
Storing a borrowed slice in a struct¶
If the struct should keep the borrowed slice instead of copying into Vec<String>, it
needs a lifetime:
struct RoomBookingSystem<'a> {
room_ids: &'a [&'a str],
meetings: Vec<Meeting>,
}
impl<'a> RoomBookingSystem<'a> {
fn new(room_ids: &'a [&'a str]) -> Self {
Self { room_ids, meetings: Vec::new() }
}
}
let ids = ["A101", "B202"];
let system = RoomBookingSystem::new(&ids); // `ids` must outlive `system`
See Lifetimes in Rust. Usually Vec<String> is simpler.
What .iter() returns¶
For room_ids: &[&str]:
iter() yields references to the elements. The elements are already &str, so each
item is &&str. Three equivalent ways to get a String out:
room_ids.iter().map(|id| (*id).to_string()) // id: &&str, *id: &str
room_ids.iter().map(|&id| id.to_string()) // pattern `&id` strips the outer &, id: &str
room_ids.iter().map(|id| id.to_string()) // autoderef finds to_string anyway
to_string() copies the borrowed text into an owned String.