Skip to content

Commit

Permalink
Auto merge of #40434 - mattico:splice-update, r=alexcrichton
Browse files Browse the repository at this point in the history
Implement Vec::splice and String::splice (RFC 1432)

RFC: rust-lang/rfcs#1432, tracking issue: #32310
A rebase of #32355 with a few more tests.

Let me know if you have any ideas for more tests.

cc @SimonSapin
  • Loading branch information
bors committed Apr 25, 2017
2 parents 0777c75 + feae5a0 commit c7e724a
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/doc/unstable-book/src/SUMMARY.md
Expand Up @@ -195,6 +195,7 @@
- [slice_rsplit](library-features/slice-rsplit.md)
- [sort_internals](library-features/sort-internals.md)
- [sort_unstable](library-features/sort-unstable.md)
- [splice](library-features/splice.md)
- [step_by](library-features/step-by.md)
- [step_trait](library-features/step-trait.md)
- [str_checked_slicing](library-features/str-checked-slicing.md)
Expand Down
24 changes: 24 additions & 0 deletions src/doc/unstable-book/src/library-features/splice.md
@@ -0,0 +1,24 @@
# `splice`

The tracking issue for this feature is: [#32310]

[#32310]: https://github.com/rust-lang/rust/issues/32310

------------------------

The `splice()` method on `Vec` and `String` allows you to replace a range
of values in a vector or string with another range of values, and returns
the replaced values.

A simple example:

```rust
#![feature(splice)]
let mut s = String::from("α is alpha, β is beta");
let beta_offset = s.find('β').unwrap_or(s.len());

// Replace the range up until the β from the string
let t: String = s.splice(..beta_offset, "Α is capital alpha; ").collect();
assert_eq!(t, "α is alpha, ");
assert_eq!(s, "Α is capital alpha; β is beta");
```
6 changes: 1 addition & 5 deletions src/libcollections/slice.rs
Expand Up @@ -1519,13 +1519,9 @@ impl<T: Clone> ToOwned for [T] {
self.to_vec()
}

// HACK(japaric): with cfg(test) the inherent `[T]::to_vec`, which is required for this method
// definition, is not available. Since we don't require this method for testing purposes, I'll
// just stub it
// NB see the slice::hack module in slice.rs for more information
#[cfg(test)]
fn to_owned(&self) -> Vec<T> {
panic!("not available with cfg(test)")
hack::to_vec(self)
}

fn clone_into(&self, target: &mut Vec<T>) {
Expand Down
125 changes: 124 additions & 1 deletion src/libcollections/string.rs
Expand Up @@ -1316,7 +1316,7 @@ impl String {
self.vec.clear()
}

/// Create a draining iterator that removes the specified range in the string
/// Creates a draining iterator that removes the specified range in the string
/// and yields the removed chars.
///
/// Note: The element range is removed even if the iterator is not
Expand Down Expand Up @@ -1382,6 +1382,71 @@ impl String {
}
}

/// Creates a splicing iterator that removes the specified range in the string,
/// replaces with the given string, and yields the removed chars.
/// The given string doesn’t need to be the same length as the range.
///
/// Note: The element range is removed when the `Splice` is dropped,
/// even if the iterator is not consumed until the end.
///
/// # Panics
///
/// Panics if the starting point or end point do not lie on a [`char`]
/// boundary, or if they're out of bounds.
///
/// [`char`]: ../../std/primitive.char.html
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(splice)]
/// let mut s = String::from("α is alpha, β is beta");
/// let beta_offset = s.find('β').unwrap_or(s.len());
///
/// // Replace the range up until the β from the string
/// let t: String = s.splice(..beta_offset, "Α is capital alpha; ").collect();
/// assert_eq!(t, "α is alpha, ");
/// assert_eq!(s, "Α is capital alpha; β is beta");
/// ```
#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
pub fn splice<'a, 'b, R>(&'a mut self, range: R, replace_with: &'b str) -> Splice<'a, 'b>
where R: RangeArgument<usize>
{
// Memory safety
//
// The String version of Splice does not have the memory safety issues
// of the vector version. The data is just plain bytes.
// Because the range removal happens in Drop, if the Splice iterator is leaked,
// the removal will not happen.
let len = self.len();
let start = match range.start() {
Included(&n) => n,
Excluded(&n) => n + 1,
Unbounded => 0,
};
let end = match range.end() {
Included(&n) => n + 1,
Excluded(&n) => n,
Unbounded => len,
};

// Take out two simultaneous borrows. The &mut String won't be accessed
// until iteration is over, in Drop.
let self_ptr = self as *mut _;
// slicing does the appropriate bounds checks
let chars_iter = self[start..end].chars();

Splice {
start: start,
end: end,
iter: chars_iter,
string: self_ptr,
replace_with: replace_with
}
}

/// Converts this `String` into a `Box<str>`.
///
/// This will drop any excess capacity.
Expand Down Expand Up @@ -2145,3 +2210,61 @@ impl<'a> DoubleEndedIterator for Drain<'a> {

#[unstable(feature = "fused", issue = "35602")]
impl<'a> FusedIterator for Drain<'a> {}

/// A splicing iterator for `String`.
///
/// This struct is created by the [`splice()`] method on [`String`]. See its
/// documentation for more.
///
/// [`splice()`]: struct.String.html#method.splice
/// [`String`]: struct.String.html
#[derive(Debug)]
#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
pub struct Splice<'a, 'b> {
/// Will be used as &'a mut String in the destructor
string: *mut String,
/// Start of part to remove
start: usize,
/// End of part to remove
end: usize,
/// Current remaining range to remove
iter: Chars<'a>,
replace_with: &'b str,
}

#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
unsafe impl<'a, 'b> Sync for Splice<'a, 'b> {}
#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
unsafe impl<'a, 'b> Send for Splice<'a, 'b> {}

#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
impl<'a, 'b> Drop for Splice<'a, 'b> {
fn drop(&mut self) {
unsafe {
let vec = (*self.string).as_mut_vec();
vec.splice(self.start..self.end, self.replace_with.bytes());
}
}
}

#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
impl<'a, 'b> Iterator for Splice<'a, 'b> {
type Item = char;

#[inline]
fn next(&mut self) -> Option<char> {
self.iter.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}

#[unstable(feature = "splice", reason = "recently added", issue = "32310")]
impl<'a, 'b> DoubleEndedIterator for Splice<'a, 'b> {
#[inline]
fn next_back(&mut self) -> Option<char> {
self.iter.next_back()
}
}
1 change: 1 addition & 0 deletions src/libcollections/tests/lib.rs
Expand Up @@ -20,6 +20,7 @@
#![feature(pattern)]
#![feature(placement_in_syntax)]
#![feature(rand)]
#![feature(splice)]
#![feature(step_by)]
#![feature(str_escape)]
#![feature(test)]
Expand Down
63 changes: 63 additions & 0 deletions src/libcollections/tests/string.rs
Expand Up @@ -419,6 +419,69 @@ fn test_drain() {
assert_eq!(t, "");
}

#[test]
fn test_splice() {
let mut s = "Hello, world!".to_owned();
let t: String = s.splice(7..12, "世界").collect();
assert_eq!(s, "Hello, 世界!");
assert_eq!(t, "world");
}

#[test]
#[should_panic]
fn test_splice_char_boundary() {
let mut s = "Hello, 世界!".to_owned();
s.splice(..8, "");
}

#[test]
fn test_splice_inclusive_range() {
let mut v = String::from("12345");
let t: String = v.splice(2...3, "789").collect();
assert_eq!(v, "127895");
assert_eq!(t, "34");
let t2: String = v.splice(1...2, "A").collect();
assert_eq!(v, "1A895");
assert_eq!(t2, "27");
}

#[test]
#[should_panic]
fn test_splice_out_of_bounds() {
let mut s = String::from("12345");
s.splice(5..6, "789");
}

#[test]
#[should_panic]
fn test_splice_inclusive_out_of_bounds() {
let mut s = String::from("12345");
s.splice(5...5, "789");
}

#[test]
fn test_splice_empty() {
let mut s = String::from("12345");
let t: String = s.splice(1..2, "").collect();
assert_eq!(s, "1345");
assert_eq!(t, "2");
}

#[test]
fn test_splice_unbounded() {
let mut s = String::from("12345");
let t: String = s.splice(.., "").collect();
assert_eq!(s, "");
assert_eq!(t, "12345");
}

#[test]
fn test_splice_forget() {
let mut s = String::from("12345");
::std::mem::forget(s.splice(2..4, "789"));
assert_eq!(s, "12345");
}

#[test]
fn test_extend_ref() {
let mut a = "foo".to_string();
Expand Down
63 changes: 63 additions & 0 deletions src/libcollections/tests/vec.rs
Expand Up @@ -579,6 +579,69 @@ fn test_drain_inclusive_out_of_bounds() {
v.drain(5...5);
}

#[test]
fn test_splice() {
let mut v = vec![1, 2, 3, 4, 5];
let a = [10, 11, 12];
v.splice(2..4, a.iter().cloned());
assert_eq!(v, &[1, 2, 10, 11, 12, 5]);
v.splice(1..3, Some(20));
assert_eq!(v, &[1, 20, 11, 12, 5]);
}

#[test]
fn test_splice_inclusive_range() {
let mut v = vec![1, 2, 3, 4, 5];
let a = [10, 11, 12];
let t1: Vec<_> = v.splice(2...3, a.iter().cloned()).collect();
assert_eq!(v, &[1, 2, 10, 11, 12, 5]);
assert_eq!(t1, &[3, 4]);
let t2: Vec<_> = v.splice(1...2, Some(20)).collect();
assert_eq!(v, &[1, 20, 11, 12, 5]);
assert_eq!(t2, &[2, 10]);
}

#[test]
#[should_panic]
fn test_splice_out_of_bounds() {
let mut v = vec![1, 2, 3, 4, 5];
let a = [10, 11, 12];
v.splice(5..6, a.iter().cloned());
}

#[test]
#[should_panic]
fn test_splice_inclusive_out_of_bounds() {
let mut v = vec![1, 2, 3, 4, 5];
let a = [10, 11, 12];
v.splice(5...5, a.iter().cloned());
}

#[test]
fn test_splice_items_zero_sized() {
let mut vec = vec![(), (), ()];
let vec2 = vec![];
let t: Vec<_> = vec.splice(1..2, vec2.iter().cloned()).collect();
assert_eq!(vec, &[(), ()]);
assert_eq!(t, &[()]);
}

#[test]
fn test_splice_unbounded() {
let mut vec = vec![1, 2, 3, 4, 5];
let t: Vec<_> = vec.splice(.., None).collect();
assert_eq!(vec, &[]);
assert_eq!(t, &[1, 2, 3, 4, 5]);
}

#[test]
fn test_splice_forget() {
let mut v = vec![1, 2, 3, 4, 5];
let a = [10, 11, 12];
::std::mem::forget(v.splice(2..4, a.iter().cloned()));
assert_eq!(v, &[1, 2]);
}

#[test]
fn test_into_boxed_slice() {
let xs = vec![1, 2, 3];
Expand Down

0 comments on commit c7e724a

Please sign in to comment.