Vector of vectors and slice of slices

I am a bit confused why i need to do such gymnastics to transform a vector of vector into a slice of slices. (see the out_buffers variable)

Any tips how one should "work" ? To me it seemed that functions with arguments that take slices would be most flexible.

Also i am surprised that a fixed length slice is not the same type as a slice (see the in_buffers variable)

fn process(out_buffers: &mut [&mut [i32]], in_buffers: &[&[i32]]) {
    out_buffers[0][0] = in_buffers[0][0];
}

fn main() {
    let mut out_buffers = vec![vec![0]];
    let in_buffers = [&[1i32; 1]];

    let mut out_buffers = out_buffers
        .iter_mut()
        .map(|v| v.as_mut_slice())
        .collect::<Vec<&mut [i32]>>();
    let out_buffers: &mut [&mut [i32]] = out_buffers.as_mut_slice();

    let in_buffers = [[1i32; 1].as_slice()];//to avoid error

    process(out_buffers, in_buffers.as_slice());
}

Think about what the memory layout of each type is. It'll quickly become clear that a contiguous array/slice of references-to-slices (which are fat pointers because slices are DSTs) doesn't have the same layout (or even size) as a contiguous array/slice of references to fixed-size arrays (which are not fat pointers because arrays are statically-sized).

2 Likes

They have different layouts. Rust doesn't do autoboxing or anything like that; fields are stored inline.

A slice ([E]) is a congiuous sequence of Es (with padding for alignment, if needed). The size of a slice is not known at compile type; it's a dynamically sized type (DST). So you can't move it by value, for example; you'll almost always use a slice behind a reference or some other kind of pointer.

A reference to a slice (&[E]) is two usizes long: a pointer, and a length. This is sometimes called a shared slice. More often it is also just called a slice, so you have to figure out what someone means from context.

A Vec<T> is three usizes long: a pointer, a length, and a capacity. The T are stored wherever the pointer points to. The T are stored in the same manner as a slice.

So:

  • A Vec<Vec<U>> (or [Vec<U>]) stores Vec<U>s contiguously; each element is three usizes long
  • A Vec<&[U]> (or [&[U]]) stores &Us contiguously; each element is two usizes long

This is why you can't just coerce a &[Vec<E>] to a &[&[E]] in-place.


What you're calling a "fixed length slice" is an array ([E; N]). Arrays do have a length known at compile-time (N), and thus a sized known at compile-time too. The length is part of the type of an array. A reference to an array is one usize long: just a pointer.

Like with slices and Vec<E>, the elements are stored contiguously.

Here's some layout diagrams, and some others (also linked from the other thread). A reference to an array would be...

           +---+---+---+---+
&[u8; 3]:  | Pointer       |
           +---+---+---+---+
             |
             V
           +---+---+---+
[u8; 3]:   | 0 | 1 | 2 |
           +---+---+---+

Depending on what you're trying to do, it may be possible via trait bounds or iterators instead of allocating a Vec<&mut [_]>.

1 Like

Thanks @quinedot and @paramagnetic for your detailed answers.

If you want the function to deal with the different slice types automatically, you can make it generic over AsMut and AsRef like this:

fn process<O, I>(out_buffers: &mut [O], in_buffers: &[I])
where
    O: AsMut<[i32]>,
    I: AsRef<[i32]>,
{
    out_buffers[0].as_mut()[0] = in_buffers[0].as_ref()[0];
}

fn main() {
    let mut out_buffers = vec![vec![0]];
    let in_buffers = [[1]];

    process(&mut out_buffers, &in_buffers);
}

The types are slightly different but this would be the most convenient way of writing your main function. Here's all the types as they're inferred:

fn main() {
    let mut out_buffers: Vec<Vec<i32>> = vec![vec![0]];
    let in_buffers: [[i32; 1]; 1] = [[1]];

    process::<Vec<i32>, [i32; 1]>(&mut out_buffers, &in_buffers);
}

If you want in_buffers to have slices of different lengths, you need to change it to be an array of slices. You'll either need to call as_slice on at least the first array, or specify the type:

let in_buffers = [[1].as_slice(), &[2, 3]]; // coerce based on the first value
let in_buffers: [&[i32]; 2] = [&[1], &[2, 3]]; // coerce with explicit type
let in_buffers = [&[1] as &[_], &[2, 3]]; // coerce with `as`

These will infer the I generic to be &[i32] instead of [i32; 1].

If writing the length is annoying, you can coerce the outer array to a slice as well.

let in_buffers: &[&[i32]] = &[&[1], &[2, 3]];
2 Likes

thank you,
I guess this solution is based on what above was called trait bound, right?

1 Like

Yep, O: AsMut<[i32]> is a trait bound.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.