Reputation: 3852
Consider:
fn main() {
// Prints 8, 8, 16
println!(
"{}, {}, {}",
std::mem::size_of::<Box<i8>>(),
std::mem::size_of::<Box<&[i8]>>(),
std::mem::size_of::<Box<[i8]>>(),
);
}
Why do owned slices take 16 bytes, but referenced slices take only 8?
Upvotes: 20
Views: 3435
Reputation: 8476
Box<T>
is basically *const T
(Actually it's a newtype around Unique<T>
, which itself is a NonNull<T>
with PhantomData<T>
(for dropck), but let's stick to *const T
for simplicity).
A pointer in Rust normally has the same size as size_of::<usize>()
except when T
is a dynamically sized type (DST). Currently, a Box<DST>
is 2 * size_of::<usize>()
in size (the exact representation is not stable at the time of writing). A pointer to a DST is called FatPtr
.
Currently, there are two kinds of DSTs: Slices and traits. A FatPtr
to a slice is defined like this:
#[repr(C)]
struct FatPtr<T> {
data: *const T,
len: usize,
}
Note: For a trait pointer, len
is replaced by a pointer to the vtable
.
With this information, your question can be answered:
Box<i8>
: i8
is a sized type => basically the same as *const i8
=> 8 bytes in size (with 64-bit pointer width)Box<[i8]>
: [i8]
is a DST => basically the same as FatPtr<i8>
=> 16 bytes in size (with 64-bit pointer width)Box<&[i8]>
: &[i8]
is not a DST. It's basically the same as *const FatPtr<i8>
=> 8 bytes in size (with 64-bit pointer width)Upvotes: 26
Reputation: 42829
The size of a reference depends on the "sizedness" of the referenced type:
A reference to an unsized type is a pointer to the memory and the size of the pointed datum. That's what is called a fat pointer:
#[repr(C)]
struct FatPtr<T> {
data: *const T,
len: usize,
}
A Box
is a special kind of pointer that points to the heap, but it is still a pointer.
Knowing that, you understand that:
Box<i8>
is 8 bytes because i8
is sized,Box<&[i8]>
is 8 bytes because a reference is sized,Box<[i8]>
is 16 bytes because a slice is unsized.Upvotes: 8