- Feature Name: non-selfexhausting_drain
- Start Date:
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
# Summary
[summary]: #summary
Add `*_nonexhausting()` variants for every `drain()` that do not eagerly consume residual items on drop of the `DrainNonexhausting` struct.
# Motivation
[motivation]: #motivation
The `drain` API is a specialized operation that combines two unrelated tasks:
1. Moving elements out of a collection without consuming it
2. Clearing the range or the entire collection, regardless of iteration
You could call it `drain_clearing`. The forced consumption isn't necessary for a safe drain,
doesn't give you more control nor is it necessarily faster.
Because of this coupling, there is currently no efficient way of moving a subset of elements out while keeping the collection, when one does not know in advance how many elements to remove.
The `drain_filter` methods recognize the need for selective removal with on-the-fly decisions.
However, `DrainFilter`, too, will eagerly exhaust itself on drop with no way of stopping.
Excess elements can be kept by hacking some state awareness into the conditional closure and always returning `false` after some point, but this is both unnecessary computation and tedious for the programmer.
More generally speaking, it's uncharacteristic for an iterator to behave in this (semi-)eager fashion by default.
`drain` is stable and so cannot be changed, but we should have a conforming iterator.
The behaviour of a clearing drain can be gained by the combination of two more generalized and orthogonal APIs:
* The lazy drain proposed in this RFC
* An iterator adapter for self-exhaustion as proposed [here](https://hackmd.io/s/B1qGDl3Fz#).
Although with less leakage on panics, as the repair code will still be called if a panic occurs during self-exhaustion.
```rust
// take only what's needed
for element in dont_waste_me.drain_nonexhausting(..) {
/* do stuff */
if condition {
break
}
}
let cherrypicked = vec.drain_filter_nonexhausting(condition)
.take(10)
.collect();
```
# Implementation
With a non-selfexhausting drain, the collection's internal structure needs to be repaired afterwards.
This is already required for `drain_filter()`.
As mentioned in the Motivation, `drain_filter_nonexhausting` can be emulated with `drain_filter(condition)` by returning `false` from `condition` for every element after some point. The regular `drain` can be emulated with `drain_filter(|_| true)`. Therefore, any collection for which `drain_filter` can exist, can also have nonexhausting drains with small adaptions.
Given that no new challenges arise, this RFC doesn't lay out a detailed plan for the implementation.
Several collections are still lacking `drain` and/or `drain_filter`, but there is a [desire to add them](https://github.com/rust-lang/rfcs/issues/2140). In their case, `drain_filter_nonexhausting()` should be implemented first as a basis for the other drains.
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
`drain_nonexhausting` is like `drain` but does not remove items from the collection that were not consumed through the iterator.
The difference between `drain_filter_nonexhausting` and `drain_filter` is the same.
# Drawbacks
[drawbacks]: #drawbacks
* Additional API surface. Internally a lot of code can be reused.
# Rationale and alternatives
[alternatives]: #alternatives
* Instead of putting methods on the collections directly, an adapter can be provided for `Drain*` structs that changes its behaviour on drop. This lowers the number of methods on the collections directly, but would be less discoverable. It requires that `Drain` and `DrainNonexhausting` iterate in identical fashion and only differ on `Drop`. This is probably not an issue and our current drains could internally use `DrainNonexhausting`.
* Make `drain_filter` nonexhaustive and don't add any `_nonexhausting` variants at all. This minimizes API surface, but the discrepancy between `drain` and `drain_filter` will be surprising.
* The proposed name is chosen for its symmetry to the `.exhausting()` adapter proposed [here](https://hackmd.io/s/B1qGDl3Fz#)
It could also be called `*_lazy` or `*_lazy_drop`. The `lazy` part may be confusing because the iterator is already lazy apart from `Drop`. Bikeshedding welcome afterwards.
# Unresolved questions
[unresolved]: #unresolved-questions