Skip to content

&[&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]:

room_ids.iter() // std::slice::Iter<'_, &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.