Would pattern matching on `Vec`'s be useful and feasible?

I'm trying to write a function that pattern matches on a slice with a non-Copy type, like this:

fn f<T>(x: Vec<T>) -> T {
    match x.as_slice() {
        &[ ... ]  // Lots of complicated slices here...
    }
}

Because it is not possible to move out of the slice, the above doesn't work for non-Copy T.

Would it be feasible to enable code like this?

fn f<T>(x: Vec<T>) -> T {
    match x { // consumes `x`
        [ ... ]  // Lots of complicated slices here...
    }
}

(I know one can pattern match on tuples from a vec::Drain, but they are only for size up to 4 (why so low?) and require that all patterns have the same length. See a related SO question.)

Do you want items to move? Because your slice pattern can capture just references, explicitly &[ref x, ref y, ..] or implicitly [x, y, ..] with pattern ergonomics.

AFAIK there's no limit to tuple pattern length, but Itertools::tuples() has only implemented lengths up to 4.

For the question of matching Vec -- that's not a special type to the compiler, so I don't think it could get such treatment. However, a Vec<T> can convert to Box<[T]>, and I think you might be able to match that with box [x, y, ..] patterns, though that's an unstable feature.

1 Like

Yes, I do. Do you have a suggestion for how I could write the patterns differently?

Exactly, I think we both mean this.

Would there be a runtime cost to that?

The only cost going from Vec to Box is that it drops excess capacity from the allocation.

I don't think this solves the issue, because a boxed slice is still a slice which can't be moved out of...

Hmm, you're right -- while box patterns do allow destructuring moves in general, it seems not for slices. I think it could be allowed though, in theory...

If you can live with a T:Default bound, you can use mem::replace to swap in a value like so:

fn consume_destructure<T: Default>(mut x: Vec<T>) -> T {
    match x.as_mut_slice() {
        [x, y] => std::mem::replace(y, T::default()),
        [x, y, .., z] => std::mem::replace(z, T::default()),
        _ => unimplemented!()
    }
}

playground

FWIW, mem::take does the same default replacement.

1 Like

Thanks for the suggestion. I have several large patterns, and it gets a bit verbose. My current choice is the following, but it still feels far from optimal.

fn consume_destructure<T>(x: Vec<T>) -> T {
    let mut x = x.into_iter();
    match [x.next(), x.next(), x.next(), x.next(), x.next(), x.next()] {
        [Some(x), Some(y), Some(z), Some(v), Some(w), None] => {
            // Use x, y, z, v, w
        },
        // Other patterns of varying lengths, with more or less `None`'s
        _ => Err(...)
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.