To stay in safe, you can swap elements to pack the valid ones at the start and truncate out the invalid ones at the end. This may not preserve the order of invalid ones, so they would be dropped in arbitrary order.
Preserving the order of the retained elements is possible with a swap-and-truncate approach, but the removed ones will get jumbled before they're dropped.
There is a way to preserve the order of both the drained elements and the retained elements in linear time and constant space using only swaps: Stable minimum space partitioning in linear time.
It's simpler to do this using unsafe code that leaves a gap of uninitialized memory during the process, just as drain_filter does.
It's actually possible to have O(1) space with into_iter + filter_map + collect. Playground. Specialization does wonders here. I think this behaviours is not guaranteed, but hey, it works!
Actually, the old vec is freed and a new one is constructed, but the storage is (in this case) reused:
when you call into_iter, the ownership of vec's storage is moved to IntoIter struct and is not dropped until the whole iterator chain is dropped.
collect, or more precisely FromIterator is specialized for Vec when following conditions are satisfied:
source iterator is vec::IntoIter,
the source type and destination type have the same layout
the only operations in the chain are map/filter/filter_map/enumerate, etc. – namely only the ones that cannot result in more destination elements than in source.
In such case specialization kicks in, that basically implements the in-place-filtering algorithm with reader index + writer index.
Does this mean that the Vec resulting from collect() will have large capacity allocated even if the number of elements collected after filtering is small?
Edit: Tested. Indeed it does. I think that's pretty bad. A very surprising waste of space. Rust Playground
Hah, actually I've tried to add some source code link in previous post, but couldn't find anything quickly enough . I believe this is a good file to look at, also this test. Seems to be powered by InPlaceIterable and SourceIter traits.
Yup, I guess that's one of the reasons this behaviour is not guaranteed to stay there.