Language around slices tends to be pretty loose, but let me present a more formal set of definitions first.
-
A slice, [T]
, is a series of T
in contiguous memory (layed out one after another, with proper alignment). It could have any length at run time; we say it is a dynamically sized type (DST), or an unsized type, or a type that does not implement Sized
.
-
A shared slice, &[T]
, is a shared reference to a slice. It's a wide reference consisting of a pointer to the memory of the slice, and the number of elements.
-
An exclusive slice, &mut [T]
, is like a shared slice only the borrow is exclusive (so you can e.g. overwrite elements through it).
But most conversation and even official documentation will refer to all of [T]
, &[T]
, and &mut [T]
as "a slice". I'm afraid you'll just have to get used to figuring out which they meant from context.
Slices ([T]
) can be on the stack, on the heap, in static memory, or whatever. They don't "care" where they are. So shared and exclusive slices may point to some variable on the stack, such as an array; or to some data on the heap, such as in a Vec
; or to some data in static memory, like a static
value or literal in your code. You might get one to borrowed data someone else owns, you might create one to data you own, or you might have one that technically no one owns because it's in static memory and lasts for the entire runtime of your program.
Shared and exclusives slices (&[T]
, &mut [T]
) never own the T
themselves, because they are references / a type of borrow.
A Vec<T>
is an owned, growable buffer that stores T
s in contiguous memory. You can conceptually think of this as something that owns a slice of [T]
. You can index into a Vec<T>
with a range and get back a shared or exclusive slice.
A String
is, under the hood, like a Vec<u8>
which has additional guarantees -- namely, that the bytes are valid UTF8. A &str
is like a &[u8]
that has the same guarantee. You can index into a String
with a range and get back a &str
(or &mut str
).
A literal &str
, like "something in double quotes"
, is a &'static str
pointing at static memory containing the contents of the literal.
As with slices, when you have a &str
, you might be borrowing something you also own (e.g. in a String
), you might be borrowing something someone else owns, or you might be borrowing something from static memory, etc.
You can treat a &str
like a &[u8]
. But you can't safely treat a &[u8]
like a &str
without checking it first, because the bytes might not be valid UTF8.
Other data structures that can be considered a form of owned slices other than Vec<T>
include:
-
[T; N]
is an array with a compile-time known length; it's like a slice ([T]
) but the length is known at compile time (so it is Sized
). The length is also part of the type. It's not growable.
-
Box<[T]>
, a "boxed slice"; this is similar to a Vec<T>
in that it owns the T
and stores them on the heap. The length is stored at runtime, and isn't known at compile time. But unlike a Vec<T>
, the buffer is not growable (or shrinkable).
-
Arc<[T]>
and Rc<[T]>
are shared owneship variations on Box<[T]>
.
-
There are other combinations too (Box<[T; N]>
, etc.)
-
And similar variations for UTF8 data (Box<str>
, Arc<str>
, Rc<str>
)
You can create shared slices to these other types of owned slices too.
Technically, just a single T
is like a [T; 1]
(it has the same layout in memory). So if you squint just right, every owned T
is also a form of owned slice with length 1. And indeed, you can create &[T]
and &mut [T]
(and array versions too) from &T
and &mut T
.