How do I pass a 2D vec as a slice to a function?

Viewed 362

For one dimension the code mockup is:

fn main() {
    let a = vec![1, 2, 3, 4, 5];
    print_it(&a); // :)
}

fn print_it(p: &[usize]) {
    println!("{:?}", p);
}

I try to apply the equivalent logic for a two dimensional vector and it doesn't work

fn main() {
    let a: Vec<Vec<usize>> = vec![
        vec![1,2,3,4,],
        vec![5,6,7,8,],
    ];
    print_it(&a); // :( expected slice `[&[usize]]`, found struct `Vec`
}

fn print_it(p: &[&[usize]] ) {
    println!("{:?}", p);
}

What am I doing wrong?

2 Answers

This conversion is not possible without extra work. From the official documentation of the slice type:

A dynamically-sized view into a contiguous sequence, [T]. Contiguous here means that elements are laid out so that every element is the same distance from its neighbors.

That means the memory that is pointed to, looks like this:

| T | T | ... | T |

Slices are a view into a block of memory represented as a pointer and a length.

That means that the type [&[usize]] could look something like this in memory:

| pointer, length | pointer, length | ... | pointer, length |

You, however, have a vector of vectors. A vector has a pointer to its allocated memory and two usizes, one for the length and one for the capacity. A [Vec<T>] is therefore

| pointer, length, capacity | pointer, length, capacity | ... | pointer, length, capacity |

The Vec<Vec<T>> has this layout in its internal buffer too. Since the latter layout of pointer-length pairs is not contiguous (the space for the capacity usize is inbetween), we cannot just reinterpret a pointer to that memory as if it was a &[&[T]].


If you really need the 2D slice, you have to allocate a new buffer that has the pointer-length layout:

let v: Vec<Vec<usize>> = todo!();
let slice2d: Vec<&[usize]> = v.iter().map(Vec::as_slice).collect();

Playground

If you just don't want to restrict your function signature to Vec, you could make it more generic:

use std::ops::Index;

fn print_it<I>(p: &[I])
where
    I: Index<usize, Output = usize>,
{
    println!("{:?}", p[0][0]);
}

Playground

The problem is not in the function signature. You need to have a Vec<&[usize]> so it can finally be a &[&[usize]]. For example, taking the slices and collecting them in another vector:

let a_refs: Vec<&[usize]> = a.iter().map(|v| v.as_slice()).collect();

Full example:

fn main() {
    let a: Vec<Vec<usize>> = vec![
        vec![1,2,3,4,],
        vec![5,6,7,8,],
    ];
    let a_refs: Vec<&[usize]> = a.iter().map(|v| v.as_slice()).collect();
    print_it(&a_refs); // :( expected slice `[&[usize]]`, found struct `Vec`
}

fn print_it(p: &[&[usize]] ) {
    println!("{:?}", p);
}

Playground

Notice that the new vector only contains references to the slices of the originals vectors.

Related