Consuming Vec::append

The Vec::append method takes a mutable reference to the other vector because it removes the items from it. I wrote a lot of Rust code, but I never needed this exact form of append. I always want to have a function that consumes a vector. A common example looks like this:

vec.append(&mut fn_returning_vec());

I would very much prefer to write vec.push_vec(fn_returning_vec()) instead. In another case, if the source vector is stored in a variable, I need to make it mut if I want to append it to something:

let mut source_vec = ...
vec.append(&mut source_vec);

It makes the code worse because the variable would otherwise be immutable, allowing to reason about its value more easily.

I understand that Vec::append is more capable than a consuming version and it makes sense to have it in std. Vec::append allows you to re-use the allocated buffer of the source vector if you need it later. However, consuming the source vector is just so much more common, in my experience.

My questions are:

  1. Is there a consuming version of Vec::append in std or in a widely used crate?
  2. Why not?
  3. Did you have a situation when you found Vec::append useful and a consuming version would not fit better?

You should be able to use vec.append(&mut {source_vec}) instead of making a temporary.

I might be missing something, but wouldn't extend do the trick? As in:

vec.extend(source_vec);

The extend method comes from Vecs implementation of Extend.

2 Likes

Yes, extend() is exactly what I wanted. It's a shame that it's very hard to find it in the documentation because it's buried in more than a hundred other trait implementations.

I know what you mean, but this is a price we pay for extensibility.

In general, it is worth going through the "implemented traits" list of all the important builtin types once - you not only learn more about what the types can do, but also get more familiar with the basic traits of std.

Well, I don't know... It's literally in the first example.

1 Like

Perhaps the documentation of append could be tweaked to mention extend. After all, it's in the prelude, and it's a lot more versatile. Legitimate use cases for append are rare, and I'd consider most of them to be premature optimization without first seeing some hard numbers from benchmarking.

Hell, even in those few legitimate use cases, I would probably choose to write vec.extend(other.drain(..)) instead. (though this relies on compiler optimization whereas append is a memcpy)

3 Likes

Why is it a premature optimization when itā€™s calling a simple existing method? :slight_smile: Given a choice to do the likely more efficient thing at the same maintenance/complexity cost as a possibly slower alternative, using the former isnā€™t premature optimization in my book. append also happens to be shorter than extend/drain combo. And itā€™ll make debug builds a bit faster.

1 Like

I meant the use-cases themselves are premature optimizations, because most of the ones I can think of require taking something that would be more naturally written as consuming a vector, and transforming the code to instead pass around a reusable output buffer.

Hmm, ok. I donā€™t think Vec reuse is all that ā€œpremature optimizationā€-y, but ok, I take your point.