Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Vec::drain from RFC 574 #24781

Merged
merged 2 commits into from Apr 29, 2015

Conversation

Projects
None yet
8 participants
@bluss
Copy link
Contributor

bluss commented Apr 24, 2015

Implement Vec::drain() from rust-lang/rfcs#574, tracking issue #23055.

This is a big step forward for vector usability. This is an introduction of an API for removing a range of m consecutive elements from a vector, as efficently as possible.

New features:

  • Introduce trait std::collections::range::RangeArgument implemented by all four built-in range types.
  • Change Vec::drain() to use Vec::drain<R: RangeArgument>(R)

Implementation notes:

  • Use @Gankro's idea for memory safety: Use set_len on the source vector when creating the iterator, to make sure that the part of the vector that will be modified is unreachable. Fix up things in Drain's destructor — but even if it doesn't run, we don't expose any moved-out-from slots of the vector.
  • This .drain<R>(R) very close to how it is specified in the RFC.
  • Introduced as unstable
  • Drain reuses the slice iterator — copying and pasting the same iterator pointer arithmetic again felt very bad
  • The usize index as a range argument in the RFC is not included. The ranges trait would have to change to accomodate it.

Please help me with:

  • Name and location of the new ranges trait.
  • Design of the ranges trait
  • Understanding Niko's comments about variance (Note: for a long time I was using a straight up &mut Vec in the iterator, but I changed this to permit reusing the slice iterator).

Previous PR and discussion: #23071

@rust-highfive

This comment has been minimized.

Copy link
Collaborator

rust-highfive commented Apr 24, 2015

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @brson (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. The way Github handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see CONTRIBUTING.md for more information.

@bluss bluss force-pushed the bluss:vec-drain-range branch from a6a721a to aa9e061 Apr 24, 2015

@brson

This comment has been minimized.

Copy link
Contributor

brson commented Apr 24, 2015

r? @aturon

@rust-highfive rust-highfive assigned aturon and unassigned brson Apr 24, 2015

Range,
RangeTo,
RangeFrom
};

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Stylistically the compiler doesn't tend to put an import-per-line like this but instead use multiple use statements.

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

One line with {RangeFull, Range, ..} for all the ranges would be the usual style then?

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Indeed! The wrapping (to a new import) just happens if it runs over 80 chars

/// A draining iterator for `Vec<T>`.
#[unstable(feature = "collections", reason = "recently added")]
pub struct Drain<'a, T: 'a>
{

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Style-wise the compiler typically puts this brace on the prior line (for here and the functions and such below)

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

Thanks, I should have checked for this! I'm somewhat of a rebel and I know it

};
self.set_len(0);
// set self.vec length's to start, to be safe in case Drain is leaked
self.set_len(start);

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Props for this, it's a clever solution!

/// Current remaining range to remove
iter: slice::Iter<'a, T>,
vec: *mut Vec<T>,
_marker: PhantomData<&'a mut Vec<T>>,

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Perhaps this could eschew the PhantomData by storing slice::IterMut<'a, T>?

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

The old PR had a discussion about that. This would affect variance, but I'm not clear on what's right. We use the iterator during iteration but then deref the *mut Vec once in the destructor to use it as &mut Vec.

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Ah that's why I was thinking of using IterMut instead of Iter (to take out the mutable borrow as opposed to a shared one). Although I'm not sure what effect that would have on this in terms of variance.

This comment has been minimized.

@bluss

bluss Apr 25, 2015

Author Contributor

I've done my variance homework and IterMut seems fine. Both &mut Vec<T> and &mut T (in IterMut) should have the same implications (invariant in T).

This comment has been minimized.

@bluss

bluss Apr 25, 2015

Author Contributor

I see why covariant in T is really possible here too. We could do that too. Just removing values from the Vec would allow it, but it feels shaky (and I don't know any example where it mattes in practice).

None => None,
Some(elt) => {
unsafe {
Some(ptr::read(elt as *const _))
}
}
}

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

This may be a good spot for a map:

self.iter.next_back().map(|ptr| unsafe {
    ptr::read(ptr)
})
/// Index of tail to preserve
tail_start: usize,
/// Length of tail
tail_len: usize,

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

In the interest of saving space, either length or start can be derived from the other, right?

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

Oh wait nevermind, the vector's length is "inaccurate" so it can't be used!

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

The vector's temporary length stores another necessary field -- the original starting point of the range we are removing.

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

Space that can be saved: The drop flag.

This comment has been minimized.

@bluss

bluss Apr 24, 2015

Author Contributor

I'm not going to attempt the drop flag without advice because I'm not up to date with how filling drop interacts with that.

}

unsafe impl<'a, T: Sync> Sync for Drain<'a, T> {}
unsafe impl<'a, T: Send> Send for Drain<'a, T> {}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T> Iterator for Drain<'a, T> {
impl<'a, T> Iterator for Drain<'a, T>
{

This comment has been minimized.

@alexcrichton

alexcrichton Apr 24, 2015

Member

There's a few next-line { characters that should move up here and below (just skimming the diff)

@bluss bluss force-pushed the bluss:vec-drain-range branch from 311a374 to bb12d20 Apr 25, 2015

@bluss

This comment has been minimized.

Copy link
Contributor Author

bluss commented Apr 25, 2015

I've addressed all review comments, thank you @alexcrichton!

Introducing this as unstable gives us leeway to fix it up later, what we could change:

  • Range trait method fn into_endpoints(self) -> (Option<T>, Option<T>) would allow interpreting usize as a n..n+1 range, but it feels bad to affix that interpretation in a general trait
  • Gankro's preferred Drain trait in the previous PR would allow to do what we do with the Index trait -- each collection determines itself which ranges and index types to handle for Drain.
  • Personally I don't like .drain(usize) because I want the API to always be range based.
  • We may consider calling the method .remove_range instead. Depends on if you want to emphasize the removal feature or the iterator feature.
use core::option::Option::{self, None, Some};
use core::ops::{RangeFull, Range, RangeTo, RangeFrom};

#[unstable(feature = "collections", reason = "was just added")]

This comment has been minimized.

@alexcrichton

alexcrichton Apr 27, 2015

Member

On second though, could you apply just one of these stability attributes to the module itself? The instability should be inherited. Additionally, could you give this a name like collections_range as well?

/// ```
#[inline]
#[unstable(feature = "collections",

This comment has been minimized.

@alexcrichton

alexcrichton Apr 27, 2015

Member

While you're at it, could you change the name of this feature to something like collections_drain?

end: *const T,
marker: PhantomData<&'a T>,
/// A draining iterator for `Vec<T>`.
#[unstable(feature = "collections", reason = "recently added")]

This comment has been minimized.

@alexcrichton

alexcrichton Apr 27, 2015

Member

Same thought about changing the feature name here as above.

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 27, 2015

Looks great to me, thanks @bluss! After some minor refinements of the feature names here I think this is good to go!

@bluss

This comment has been minimized.

Copy link
Contributor Author

bluss commented Apr 27, 2015

I'm excited. I'm working on checking that it compiles and then squashing it together.

@bluss bluss force-pushed the bluss:vec-drain-range branch from be84f11 to 6e5f8ce Apr 27, 2015

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 27, 2015

Thanks! Lemme know when it's ready to go and I'll r+

@bluss bluss force-pushed the bluss:vec-drain-range branch from 6e5f8ce to b7d1ad9 Apr 27, 2015

Ulrik Sverdrup
collections: Add trait RangeArgument
RangeArgument is introduced as unstable under the
feature(collections_range)

@bluss bluss force-pushed the bluss:vec-drain-range branch from b7d1ad9 to 447ef06 Apr 27, 2015

@bluss

This comment has been minimized.

Copy link
Contributor Author

bluss commented Apr 27, 2015

There, it should be ready. I could have missed something, haven't added new feature flags before.

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 27, 2015

@bors: r+ 447ef06

Thanks!

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Apr 28, 2015

⌛️ Testing commit 447ef06 with merge dc19940...

bors added a commit that referenced this pull request Apr 28, 2015

Auto merge of #24781 - bluss:vec-drain-range, r=alexcrichton
Implement Vec::drain(\<range type\>) from rust-lang/rfcs#574, tracking issue #23055.

This is a big step forward for vector usability. This is an introduction of an API for removing a range of *m* consecutive elements from a vector, as efficently as possible.

New features:

- Introduce trait `std::collections::range::RangeArgument` implemented by all four built-in range types.
- Change `Vec::drain()` to use `Vec::drain<R: RangeArgument>(R)`

Implementation notes:

- Use @Gankro's idea for memory safety: Use `set_len` on the source vector when creating the iterator, to make sure that the part of the vector that will be modified is unreachable. Fix up things in Drain's destructor — but even if it doesn't run, we don't expose any moved-out-from slots of the vector.
- This `.drain<R>(R)` very close to how it is specified in the RFC.
- Introduced as unstable
- Drain reuses the slice iterator — copying and pasting the same iterator pointer arithmetic again felt very bad
- The `usize` index as a range argument in the RFC is not included. The ranges trait would have to change to accomodate it.

Please help me with:

- Name and location of the new ranges trait.
- Design of the ranges trait
- Understanding Niko's comments about variance (Note: for a long time I was using a straight up &mut Vec in the iterator, but I changed this to permit reusing the slice iterator).

Previous PR and discussion: #23071
@bors

This comment has been minimized.

Copy link
Contributor

bors commented Apr 28, 2015

💔 Test failed - auto-mac-64-opt

Ulrik Sverdrup
collections: Implement vec::drain(range) according to RFC 574
Old `.drain()` on vec is performed using `.drain(..)` now.

`.drain(range)` is unstable and under feature(collections_drain)

[breaking-change]

@bluss bluss force-pushed the bluss:vec-drain-range branch from 447ef06 to b475fc7 Apr 28, 2015

@bluss

This comment has been minimized.

Copy link
Contributor Author

bluss commented Apr 28, 2015

Fixed that test.

@alexcrichton

This comment has been minimized.

Copy link
Member

alexcrichton commented Apr 28, 2015

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Apr 28, 2015

⌛️ Testing commit b475fc7 with merge 8871c17...

bors added a commit that referenced this pull request Apr 28, 2015

Auto merge of #24781 - bluss:vec-drain-range, r=alexcrichton
Implement Vec::drain(\<range type\>) from rust-lang/rfcs#574, tracking issue #23055.

This is a big step forward for vector usability. This is an introduction of an API for removing a range of *m* consecutive elements from a vector, as efficently as possible.

New features:

- Introduce trait `std::collections::range::RangeArgument` implemented by all four built-in range types.
- Change `Vec::drain()` to use `Vec::drain<R: RangeArgument>(R)`

Implementation notes:

- Use @Gankro's idea for memory safety: Use `set_len` on the source vector when creating the iterator, to make sure that the part of the vector that will be modified is unreachable. Fix up things in Drain's destructor — but even if it doesn't run, we don't expose any moved-out-from slots of the vector.
- This `.drain<R>(R)` very close to how it is specified in the RFC.
- Introduced as unstable
- Drain reuses the slice iterator — copying and pasting the same iterator pointer arithmetic again felt very bad
- The `usize` index as a range argument in the RFC is not included. The ranges trait would have to change to accomodate it.

Please help me with:

- Name and location of the new ranges trait.
- Design of the ranges trait
- Understanding Niko's comments about variance (Note: for a long time I was using a straight up &mut Vec in the iterator, but I changed this to permit reusing the slice iterator).

Previous PR and discussion: #23071

@bors bors merged commit b475fc7 into rust-lang:master Apr 29, 2015

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details

@bluss bluss deleted the bluss:vec-drain-range branch Apr 29, 2015

@bors bors referenced this pull request Apr 29, 2015

Merged

Register new snapshots #24888

@Stebalien

This comment has been minimized.

Copy link
Contributor

Stebalien commented Apr 30, 2015

So I know I'm starting to sound like a broken record but... RangeArgument isn't going to play well with inclusive ranges. Would you consider changing it to something like Bounds (sometime before stabilization)?

use std::collections::Bound;
trait Bounds {
    fn start(&self) -> Bound<&T>;
    fn end(&self) -> Bound<&T>;
}
impl<T> Bounds for Range<T> {
    fn start(&self) -> Bound<&T> { Bound::Included(&self.start) }
    fn start(&self) -> Bound<&T> { Bound::Excluded(&self.end) }
}
// ...

I suggest adding an into_bounds(self) -> (Bound<T>, Bound<T>) method for extracting the endpoints by value.

@bluss

This comment has been minimized.

Copy link
Contributor Author

bluss commented Apr 30, 2015

I agree it's not ready for stabilization. Specifically the design of the trait over ranges, and then we should impl drain for more collections to see how the API holds up.

@glaebhoerl

This comment has been minimized.

Copy link
Contributor

glaebhoerl commented May 6, 2015

RangeArgument trait brainstorming:

trait IsRange {
    type Idx;
    fn as_range(&self)         -> Range<Option<&Idx>>;
    fn as_mut_range(&mut self) -> Range<Option<&mut Idx>>;
    fn into_range(self)        -> Range<Option<Idx>>;
}
  • I feel like an associated type is more appropriate here? The existing range types all have an index type that's uniquely determined by the type of the range.
  • Instead of a tuple or separate getters, we can use Range itself. I find this appealing aesthetically, though I'm not sure about the ergonomics. This could potentially have positive interactions if we were to give the Range types fleshed-out APIs with inherent impls (e.g. range intersection, smallest-containing-range, ...), as befits their status as built-in types.
  • I'm not sure if there's a way to abstract the three very similar methods into just one, or if not, whether the single trait should perhaps be decomposed into a hierarchy (a la the other Foo, FooMut traits). (If the only implementors are going to be the built-in range types, perhaps not.)
  • I don't like the name RangeArgument. Passing any range type as an argument is a particular use case, but not what the trait is. I'd most like to call it simply Range, but the conflict with struct Range would probably be too annoying. (I don't really like IsRange either - it's just a bad compromise after I couldn't think of anything better.)
  • Can we just use convert::{As, AsMut, Into} instead? (Maybe if we had trait aliases to make it ergonomic...?)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.