Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Box<[T]> need 16 bytes in memory, but a referenced slice needs only 8? (on x64 machine)

Tags:

rust

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?

like image 942
amin Avatar asked Apr 23 '19 15:04

amin


People also ask

What is Box for Rust?

All values in Rust are stack allocated by default. Values can be boxed (allocated on the heap) by creating a Box<T> . A box is a smart pointer to a heap allocated value of type T . When a box goes out of scope, its destructor is called, the inner object is destroyed, and the memory on the heap is freed.

Is a box a pointer Rust?

Because a Box<T> is a pointer, Rust always knows how much space a Box<T> needs: a pointer's size doesn't change based on the amount of data it's pointing to.


2 Answers

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 those 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)
like image 142
Tim Diekmann Avatar answered Oct 11 '22 19:10

Tim Diekmann


The size of a reference depends on the "sizedness" of the referenced type:

  • A reference to a sized type is a single pointer to the memory address.
  • 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.
like image 34
Boiethios Avatar answered Oct 11 '22 20:10

Boiethios