2016-02-13 01:31:29 +08:00
|
|
|
|
- Feature Name: splice
|
2015-12-29 02:16:58 +08:00
|
|
|
|
- Start Date: 2015-12-28
|
2016-03-18 00:24:16 +08:00
|
|
|
|
- RFC PR: [rust-lang/rfcs#1432](https://github.com/rust-lang/rfcs/pull/1432)
|
|
|
|
|
- Rust Issue: [rust-lang/rust#32310](https://github.com/rust-lang/rust/issues/32310)
|
2015-12-29 02:16:58 +08:00
|
|
|
|
|
|
|
|
|
# Summary
|
|
|
|
|
[summary]: #summary
|
|
|
|
|
|
2016-02-13 01:31:29 +08:00
|
|
|
|
Add a `splice` method to `Vec<T>` and `String` removes a range of elements,
|
2015-12-29 02:16:58 +08:00
|
|
|
|
and replaces it in place with a given sequence of values.
|
|
|
|
|
The new sequence does not necessarily have the same length as the range it replaces.
|
2016-02-13 01:53:01 +08:00
|
|
|
|
In the `Vec` case, this method returns an iterator of the elements being moved out, like `drain`.
|
|
|
|
|
|
2015-12-29 02:16:58 +08:00
|
|
|
|
|
|
|
|
|
# Motivation
|
|
|
|
|
[motivation]: #motivation
|
|
|
|
|
|
|
|
|
|
An implementation of this operation is either slow or dangerous.
|
|
|
|
|
|
|
|
|
|
The slow way uses `Vec::drain`, and then `Vec::insert` repeatedly.
|
|
|
|
|
The latter part takes quadratic time:
|
|
|
|
|
potentially many elements after the replaced range are moved by one offset
|
|
|
|
|
potentially many times, once for each new element.
|
|
|
|
|
|
|
|
|
|
The dangerous way, detailed below, takes linear time
|
|
|
|
|
but involves unsafely moving generic values with `std::ptr::copy`.
|
|
|
|
|
This is non-trivial `unsafe` code, where a bug could lead to double-dropping elements
|
|
|
|
|
or exposing uninitialized elements.
|
|
|
|
|
(Or for `String`, breaking the UTF-8 invariant.)
|
|
|
|
|
It therefore benefits form having a shared, carefully-reviewed implementation
|
|
|
|
|
rather than leaving it to every potential user to do it themselves.
|
|
|
|
|
|
|
|
|
|
While it could be an external crate on crates.io,
|
|
|
|
|
this operation is general-purpose enough that I think it belongs in the standard library,
|
|
|
|
|
similar to `Vec::drain`.
|
|
|
|
|
|
|
|
|
|
# Detailed design
|
|
|
|
|
[design]: #detailed-design
|
|
|
|
|
|
|
|
|
|
An example implementation is below.
|
|
|
|
|
|
|
|
|
|
The proposal is to have inherent methods instead of extension traits.
|
|
|
|
|
(Traits are used to make this testable outside of `std`
|
|
|
|
|
and to make a point in Unresolved Questions below.)
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
#![feature(collections, collections_range, str_char)]
|
|
|
|
|
|
|
|
|
|
extern crate collections;
|
|
|
|
|
|
|
|
|
|
use collections::range::RangeArgument;
|
|
|
|
|
use std::ptr;
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
trait VecSplice<T> {
|
|
|
|
|
fn splice<R, I>(&mut self, range: R, iterable: I) -> Splice<I>
|
|
|
|
|
where R: RangeArgument<usize>, I: IntoIterator<Item=T>;
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
impl<T> VecSplice<T> for Vec<T> {
|
|
|
|
|
fn splice<R, I>(&mut self, range: R, iterable: I) -> Splice<I>
|
|
|
|
|
where R: RangeArgument<usize>, I: IntoIterator<Item=T>
|
2015-12-29 02:16:58 +08:00
|
|
|
|
{
|
2016-02-13 01:53:01 +08:00
|
|
|
|
unimplemented!() // FIXME: Fill in when exact semantics are decided.
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
struct Splice<I: IntoIterator> {
|
|
|
|
|
vec: &mut Vec<I::Item>,
|
|
|
|
|
range: Range<usize>
|
|
|
|
|
iter: I::IntoIter,
|
|
|
|
|
// FIXME: Fill in when exact semantics are decided.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<I: IntoIterator> Iterator for Splice<I> {
|
|
|
|
|
type Item = I::Item;
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
unimplemented!() // FIXME: Fill in when exact semantics are decided.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<I: IntoIterator> Drop for Splice<I> {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
unimplemented!() // FIXME: Fill in when exact semantics are decided.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait StringSplice {
|
2016-02-13 01:31:29 +08:00
|
|
|
|
fn splice<R>(&mut self, range: R, s: &str) where R: RangeArgument<usize>;
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
impl StringSplice for String {
|
2016-02-13 01:31:29 +08:00
|
|
|
|
fn splice<R>(&mut self, range: R, s: &str) where R: RangeArgument<usize> {
|
2015-12-29 02:16:58 +08:00
|
|
|
|
if let Some(&start) = range.start() {
|
|
|
|
|
assert!(self.is_char_boundary(start));
|
|
|
|
|
}
|
|
|
|
|
if let Some(&end) = range.end() {
|
|
|
|
|
assert!(self.is_char_boundary(end));
|
|
|
|
|
}
|
|
|
|
|
unsafe {
|
|
|
|
|
self.as_mut_vec()
|
2016-02-13 01:31:29 +08:00
|
|
|
|
}.splice(range, s.bytes())
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn it_works() {
|
|
|
|
|
let mut v = vec![1, 2, 3, 4, 5];
|
2016-02-13 01:31:29 +08:00
|
|
|
|
v.splice(2..4, [10, 11, 12].iter().cloned());
|
2015-12-29 02:16:58 +08:00
|
|
|
|
assert_eq!(v, &[1, 2, 10, 11, 12, 5]);
|
2016-02-13 01:31:29 +08:00
|
|
|
|
v.splice(1..3, Some(20));
|
2015-12-29 02:16:58 +08:00
|
|
|
|
assert_eq!(v, &[1, 20, 11, 12, 5]);
|
|
|
|
|
let mut s = "Hello, world!".to_owned();
|
2016-02-13 01:31:29 +08:00
|
|
|
|
s.splice(7.., "世界!");
|
2015-12-29 02:16:58 +08:00
|
|
|
|
assert_eq!(s, "Hello, 世界!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic]
|
|
|
|
|
fn char_boundary() {
|
|
|
|
|
let mut s = "Hello, 世界!".to_owned();
|
2016-02-13 01:31:29 +08:00
|
|
|
|
s.splice(..8, "")
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
The elements of the vector after the range first be moved by an offset of
|
|
|
|
|
the lower bound of `Iterator::size_hint` minus the length of the range.
|
|
|
|
|
Then, depending on the real length of the iterator:
|
|
|
|
|
|
|
|
|
|
* If it’s the same as the lower bound, we’re done.
|
|
|
|
|
* If it’s lower than the lower bound (which was then incorrect), the elements will be moved once more.
|
|
|
|
|
* If it’s higher, the extra iterator items well be collected into a temporary `Vec`
|
|
|
|
|
in order to know exactly how many there are, and the elements after will be moved once more.
|
2015-12-29 02:16:58 +08:00
|
|
|
|
|
|
|
|
|
# Drawbacks
|
|
|
|
|
[drawbacks]: #drawbacks
|
|
|
|
|
|
|
|
|
|
Same as for any addition to `std`:
|
2022-10-08 13:44:57 +08:00
|
|
|
|
not every program needs it, and standard library growth has a maintenance cost.
|
2015-12-29 02:16:58 +08:00
|
|
|
|
|
|
|
|
|
# Alternatives
|
|
|
|
|
[alternatives]: #alternatives
|
|
|
|
|
|
|
|
|
|
* Status quo: leave it to every one who wants this to do it the slow way or the dangerous way.
|
|
|
|
|
* Publish a crate on crates.io.
|
|
|
|
|
Individual crates tend to be not very discoverable,
|
|
|
|
|
so not this situation would not be so different from the status quo.
|
|
|
|
|
|
|
|
|
|
# Unresolved questions
|
|
|
|
|
[unresolved]: #unresolved-questions
|
|
|
|
|
|
2016-02-13 01:53:01 +08:00
|
|
|
|
* Should the input iterator be consumed incrementally at each `Splice::next` call,
|
|
|
|
|
or only in `Splice::drop`?
|
2015-12-29 02:16:58 +08:00
|
|
|
|
|
2016-02-13 01:31:29 +08:00
|
|
|
|
* It would be nice to be able to `Vec::splice` with a slice
|
2015-12-29 02:16:58 +08:00
|
|
|
|
without writing `.iter().cloned()` explicitly.
|
|
|
|
|
This is possible with the same trick as for the `Extend` trait
|
|
|
|
|
([RFC 839](https://github.com/rust-lang/rfcs/blob/master/text/0839-embrace-extend-extinguish.md)):
|
|
|
|
|
accept iterators of `&T` as well as iterators of `T`:
|
|
|
|
|
|
|
|
|
|
```rust
|
2016-02-13 01:53:01 +08:00
|
|
|
|
impl<'a, T: 'a> VecSplice<&'a T> for Vec<T> where T: Copy {
|
2016-02-13 01:31:29 +08:00
|
|
|
|
fn splice<R, I>(&mut self, range: R, iterable: I)
|
2016-02-13 01:53:01 +08:00
|
|
|
|
where R: RangeArgument<usize>, I: IntoIterator<Item=&'a T>
|
2015-12-29 02:16:58 +08:00
|
|
|
|
{
|
2016-02-13 01:31:29 +08:00
|
|
|
|
self.splice(range, iterable.into_iter().cloned())
|
2015-12-29 02:16:58 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
However, this trick can not be used with an inherent method instead of a trait.
|
|
|
|
|
(By the way, what was the motivation for `Extend` being a trait rather than inherent methods,
|
|
|
|
|
before RFC 839?)
|
|
|
|
|
|
2015-12-29 18:59:10 +08:00
|
|
|
|
* If coherence rules and backward-compatibility allow it,
|
|
|
|
|
this functionality could be added to `Vec::insert` and `String::insert`
|
|
|
|
|
by overloading them / making them more generic.
|
2015-12-29 19:08:01 +08:00
|
|
|
|
This would probably require implementing `RangeArgument` for `usize`
|
|
|
|
|
representing an empty range,
|
|
|
|
|
though a range of length 1 would maybe make more sense for `Vec::drain`
|
|
|
|
|
(another user of `RangeArgument`).
|