13

I was expecting a Vec::insert_slice(index, slice) method — a solution for strings (String::insert_str()) does exist.

I know about Vec::insert(), but that inserts only one element at a time, not a slice. Alternatively, when the prepended slice is a Vec one can append to it instead, but this does not generalize. The idiomatic solution probably uses Vec::splice(), but using iterators as in the example makes me scratch my head.

Secondly, the whole concept of prepending has seemingly been exorcised from the docs. There isn't a single mention. I would appreciate comments as to why. Note that relatively obscure methods like Vec::swap_remove() do exist.

My typical use case consists of indexed byte strings.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user103185
  • 925
  • 2
  • 8
  • 20
  • 2
    [Efficiently insert multiple elements in the middle of a Vec](https://stackoverflow.com/questions/28678615/efficiently-insert-multiple-elements-in-the-middle-of-a-vec) ? – trent Oct 31 '17 at 14:36
  • Yes, prepending is a special case. – user103185 Oct 31 '17 at 15:20
  • @trentcl good eye! Do you think we should mark as a duplicate? – Shepmaster Oct 31 '17 at 15:52
  • 2
    @Shepmaster You could, but the prepend is an essential keyword. This question already is second on Google when searching rust "prepend slice to vec". The first result is wrong (deals with appending). – user103185 Oct 31 '17 at 17:05
  • @user103185 sure, that's why SO leaves duplicate questions around — to be used as signposts with extra keywords pointing to the canonical answer, regardless of what keywords it has. – Shepmaster Oct 31 '17 at 17:09
  • @user103185 Just a thought: `Vec` takes ownership of any `T` you put in it, but slices (`&[T]`) don't own `T`s to give away. Therefore any safe implementation of `insert_slice` would copy the contents and have to have at least a `T: Clone` bound. When you want to move instead of copy, prepending is only possible when the thing you would prepend *also* owns its elements -- and when it's a `Vec`, you could just do `b.extend(a)` instead of `a.prepend(b)`. – trent Oct 31 '17 at 19:45
  • @trentcl I alluded to the option of appending if the slice were really a Vec, you have made that explicit to those who read this. As a sidenote, though we abstractly talk of Vec, a popular use is ANSI (byte) strings: prepending and insertion should be obvious (not so). – user103185 Nov 01 '17 at 14:21

2 Answers2

10

String::insert_str makes use of the fact that a string is essentially a Vec<u8>. It reallocates the underlying buffer, moves all the initial bytes to the end, then adds the new bytes to the beginning.

This is not generally safe and can not be directly added to Vec because during the copy the Vec is no longer in a valid state — there are "holes" in the data.

This doesn't matter for String because the data is u8 and u8 doesn't implement Drop. There's no such guarantee for an arbitrary T in a Vec, but if you are very careful to track your state and clean up properly, you can do the same thing — this is what splice does!

the whole concept of prepending has seemingly been exorcised

I'd suppose this is because prepending to a Vec is a poor idea from a performance standpoint. If you need to do it, the naïve case is straight-forward:

fn prepend<T>(v: Vec<T>, s: &[T]) -> Vec<T>
where
    T: Clone,
{
    let mut tmp: Vec<_> = s.to_owned();
    tmp.extend(v);
    tmp
}

This has a bit higher memory usage as we need to have enough space for two copies of v.

The splice method accepts an iterator of new values and a range of values to replace. In this case, we don't want to replace anything, so we give an empty range of the index we want to insert at. We also need to convert the slice into an iterator of the appropriate type:

let s = &[1, 2, 3];
let mut v = vec![4, 5];

v.splice(0..0, s.iter().cloned());

splice's implementation is non-trivial, but it efficiently does the tracking we need. After removing a chunk of values, it then reuses that chunk of memory for the new values. It also moves the tail of the vector around (maybe a few times, depending on the input iterator). The Drop implementation of Slice ensures that things will always be in a valid state.


I'm more surprised that VecDeque doesn't support it, as it's designed to be more efficient about modifying both the head and tail of the data.

trent
  • 25,033
  • 7
  • 51
  • 90
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for the "holes" explanation. Note there is a splice method, and also RFC's related to the question (1964 and 1432). – user103185 Oct 31 '17 at 15:31
  • @user103185 ah, `splice` is very new and I missed it in your original question; updated to include it. – Shepmaster Oct 31 '17 at 15:36
  • There is an issue for adding `splice` and `extend_front` to `VecDeque`, though it hasn’t seen much activity so far: https://github.com/rust-lang/rust/issues/69939 – Lucas Werkmeister Dec 23 '20 at 09:23
1

Taking into consideration what Shepmaster said, you could implement a function prepending a slice with Copyable elements to a Vec just like String::insert_str() does in the following way:

use std::ptr;

unsafe fn prepend_slice<T: Copy>(vec: &mut Vec<T>, slice: &[T]) {
    let len = vec.len();
    let amt = slice.len();
    vec.reserve(amt);

    ptr::copy(vec.as_ptr(),
              vec.as_mut_ptr().offset((amt) as isize),
              len);
    ptr::copy(slice.as_ptr(),
              vec.as_mut_ptr(),
              amt);
    vec.set_len(len + amt);
}

fn main() {
    let mut v = vec![4, 5, 6];

    unsafe { prepend_slice(&mut v, &[1, 2, 3]) }

    assert_eq!(&v, &[1, 2, 3, 4, 5, 6]);
}
ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • 1
    I don't know enough, but I'd be worried about zero-sized types... – Shepmaster Oct 31 '17 at 14:38
  • @Shepmaster: interesting point; I tested it on an empty struct (which worked), but I'm not sure if this covers all possibilities. I guess it should be safe for the regular primitives, though. – ljedrz Oct 31 '17 at 14:45