Is storing an unsized local into a struct bad idea?

If so, why?

struct MyStruct {
    bytes: [u8]

fn main() {
    let bytes = [0,1,2] as [u8];
    let len0 = bytes.len();
    let wrapper = MyStruct { bytes };
    assert_eq!(len0, wrapper.bytes.len()); // Does this return true?


My guess is that this is a bad idea, possibly because an unsized type does not allocate some bytes to store the length of the slice? Or, do unsized locals implicitly contains their length somewhere within?

Unsized local is a unstable feature so it's hard to say its semantics now. But I'm sure even with this feature, Rust never heap-allocate implicitly. Maybe it has its length somewhere in the stack?


This code doesn't compile, so I'm not sure what you're asking.

The solution has thus been converged to! Congratulations


fn mergesort<T: Ord>(a: &mut [T]) {
    let mut tmp = [T; dyn a.len()];
    // ...

fn main() {
    let mut a = [3, 1, 5, 6];
    mergesort(&mut a);
    assert_eq!(a, [1, 3, 5, 6]);

However, let us consider the above

Still not compiles, I don't think Rust will support VLA in near future.

Yeah, looks like unsized locals are nonlocal to rust's domain.

The RFC doesn't allow to store unsized locals in struct.
This compiles fine:


fn len(slice: [u8]) {

fn main() {
    let boxed: Box<[_]> = Box::new([0, 1, 2]);

It uses [T]::len as usual by taking a reference to the slice. Somehow the compiler is able to make a fat pointer again.

the compiler simply extends the stack frame every time it encounters an unsized assignment

So maybe the length is stored there? But I couldn't find it using "pointer exploration".


Did you play around with a range (e.g., -2, -1, 0, 1, 2) relative to the pointer's offset? Perhaps the stack frame is multiple bytes, and is thus likely in LittleEndian form?


unsafe { LittleEndian::read_u16(*pointer.offset(0), *pointer.offset(1)) };

although, those offset constants may depend on the #repr too... #repr(C) != #repr(packed) etc

Another idea is that if it isn't stored anywhere locally nearby the memory address, then it could also exist in a virtual memory table or something

I used a [usize] and went 2 (maybe 3) before and after the slice but never got the length.
I also looked into the Mir output and all it says is it's making a reference.
And the assembly (I can't read assembly) but I tried to compare the output between a slice and a slice pointer but the output is identical. That's so weird.

1 Like


So with this:

fn len(int1: u64, slice: [u64], int2: u64) {
    println!("int 1: {:p}", &int3);
    println!("slice: {:p}", &slice[0]);
    println!("int 2: {:p}", &int4);

We have int2 - int1 == 24 bytes, 8 bytes for int1 and 16 for a fat pointer. I tried different number of elements in the slice: no change.
And this display exactly want we'd expect:

fn len(int3: u64, slice: [u64]) {
    unsafe {
        println!("ptr: {:x}", &*(&int3 as *const u64).add(1));
        println!("len: {}", &*(&int3 as *const u64).add(2));
        println!("slice: {:p}", &slice[0]);

Going further with this:


fn len(int3: u64, slice: [u64], int4: u64) {
    let int5 = 0;
    println!("int 3: {:p}", &int3);
    println!("slice: {:p}", &slice[0]);
    println!("int 4: {:p}", &int4);
    println!("int 5: {:p}", &int5);

fn main() {
    let int1 = 0;
    let boxed: Box<[_]> = Box::new([0, 1, 2, 3]);
    len(0, *boxed, 0);
    let int2 = 0;
    println!("int 1: {:p}", &int1);
    println!("int 2: {:p}", &int2);

I think slice is stored between main and len's stack frame.

1 Like

I think you're right. I think someone like @RalfJung would know the answer to this

Sorry, I don't know how unsized locals are implemented in our LLVM backend. I just know how they are implemented in Miri. :wink:

Gotta got go lower down to the metal Ralf. You haven't reached enlightenment yet :wink: