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

libs: Add borrow module, deprecate _equiv and friends #18910

Merged
merged 7 commits into from
Nov 18, 2014

Conversation

aturon
Copy link
Member

@aturon aturon commented Nov 13, 2014

Following the collections reform RFC, this PR:

  • Adds a new borrow module to libcore. The module contains traits for borrowing data (BorrowFrom and BorrowFromMut), generalized cloning (ToOwned), and a clone-on-write smartpointer (Cow).
  • Deprecates the _equiv family of methods on HashMap and HashSet by instead generalizing the "normal" methods like get and remove to use the new std::borrow infrastructure.
  • Generalizes TreeMap, TreeSet, BTreeMap and BTreeSet to use the new std::borrow infrastructure for lookups.

[breaking-change]

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

cc #18424
cc @gankro

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

Note that the design here is somewhat different than the one in the RFC. This was necessary in order to overcome some language limitations: using Borrow instead of BorrowFrom requires full method-level where clauses, which are not yet available.

This will be noted in the tracking issue, and made part of a general amendment to the RFC with implementation tweaks.

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

Also, the deprecation of MaybeOwned in favor of Cow will be left for follow-up work.

@nikomatsakis
Copy link
Contributor

Nifty.

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

Another note: the RFC did not mention generalizing the indexing notation using the borrow traits, but of course that only makes sense. This PR does that as well. (Thanks @nikomatsakis for pointing out that it should.)

pub fn get(&self, key: &K) -> Option<&V> {
tree_find_with(&self.root, |k2| key.cmp(k2))
pub fn get<Sized? Q>(&self, key: &Q) -> Option<&V>
where Q: BorrowFrom<K> + Ord {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting stylistic precedent to line up the where and fn keywords, I would have expected the alignment of where to match the alignment of -> if it came on the next line which I thought was the ( in the argument list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know :-) generally we need to actually firm up all our style conventions, but that's of course lower priority than API issues. I'm happy to go with your recommended style here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'd just do a double-indent, like a normal line continuation.

@alexcrichton
Copy link
Member

Is there a reason you chose BorrowFrom{,Mut} instead of Borrow{,Mut}? (like in the RFC)

}

#[unstable = "trait is unstable"]
impl<T> BorrowFromMut<Vec<T>> for [T] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be in vec.rs? Genuinely unsure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the BorrowFrom and BorrowFromMut traits are in libcore, they cannot implement for Vec there. Sincce these implementations can't be anywhere else, surely this is the right place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could be in the vec module in libcollections. I put them here because they are being applied to the slice type, and I feel like putting impls in the module defining their Self type is a good default.

@nikomatsakis
Copy link
Contributor

@aturon it occurs to me that we may be able to implement PathBuf : BorrowFrom<Path> and thus have a hashmap keyed on PathBuf be indexable with a Path. The blanket impl wouldn't conflict because it requires that the two types be equal, and PathBuf != Path. I'm not sure how it would interact with inference.

//!
//! Some types make it possible to go from borrowed to owned, usually by
//! implementing the `Clone` trait. But `Clone` works only for going from `&T`
//! to `T`. The `ToOwned` trait generalizes `Clone` to contruct owned data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/contruct/construct

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

@alexcrichton Note the comments above re: differences from the RFC.

@aturon
Copy link
Member Author

aturon commented Nov 13, 2014

@nikomatsakis

@aturon it occurs to me that we may be able to implement PathBuf : BorrowFrom<Path> and thus have a hashmap keyed on PathBuf be indexable with a Path. The blanket impl wouldn't conflict because it requires that the two types be equal, and PathBuf != Path. I'm not sure how it would interact with inference.

Yes! That's very much the plan. Note that the blanket impl implicitly requires sized types, which is enough to avoid the overlap.

fn borrow_from(owned: &T) -> &T { owned }
}

impl BorrowFrom<&'static str> for str {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this necessary for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows you to use things like HashMap<&'static str, T> effectively. However, I don't see any reason to not generalize this to impl<'a, Sized? T> BorrowFrom<&'a T> for T.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: there was an issue a while ago (that I just spent far too long failing to find) about the fact that there was no good way to use data structures keyed on reference types like &'static str.

@Gankra
Copy link
Contributor

Gankra commented Nov 13, 2014

\o/ This is wonderful! 😍

fn borrow_from_mut(owned: &mut Owned) -> &mut Self;
}

impl<T: Sized> BorrowFrom<T> for T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the RFC said to use T: Sized here to prevent overlap with the other impls, but now that we have multidispatch this shouldn't be a problem, right?

@Gankra
Copy link
Contributor

Gankra commented Nov 13, 2014

You seem to have forgotten btree.

@Gankra
Copy link
Contributor

Gankra commented Nov 13, 2014

Possible thing to include: tuple of Owned -> tuple of Borrowed impls.

@@ -1045,7 +997,7 @@ impl<K: Eq + Hash<S>, V, S, H: Hasher<S>> HashMap<K, V, H> {
/// assert_eq!(map.get(&2), None);
/// ```
#[unstable = "matches collection reform specification, waiting for dust to settle"]
pub fn get(&self, k: &K) -> Option<&V> {
pub fn get<Sized? Q>(&self, k: &Q) -> Option<&V> where Q: Hash<S> + Eq + BorrowFrom<K> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this assumes that the implementations of Hash and Eq for Q and K are compatible, right? That is, for any k1 and k2 of type K:

let q1: Q = BorrowFrom::borrow_from(k1);
let q2: Q = BorrowFrom::borrow_from(k2);
assert_eq!(make_hash(q1), make_hash(k1));
assert_eq!(q1 == q2, k1 == k2);

That invariant unfortunately doesn’t seem like it can be enforced by the type system, but maybe it should be documented?

The problem was the same with Equiv. We ran into it in Servo when trying to make hashing of interned strings O(1) by only hashing the internal u64.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was a tradeoff brought up in the RFC. It's possible to enforce this invariant, but at some loss in flexibility: it would force you to consistently use a single kind of borrowed value for lookups.

Agreed about documentation.

@aturon aturon force-pushed the borrow-traits branch 3 times, most recently from b55a578 to 9c9615a Compare November 14, 2014 00:20
@aturon
Copy link
Member Author

aturon commented Nov 14, 2014

@gankro @alexcrichton

Pushed a new version; I believe I've addressed all comments.

@thestinger
Copy link
Contributor

(C++14 provides methods like find_with, so there is precedence for it)

@pythonesque
Copy link
Contributor

Any way Cow could not be done through deref(mut)? It was pointed out on IRC that this would be Rust's first "costly" use of those traits, which at least to me seems like it's sufficient to negate the syntactic simplicity of magic.

@alexcrichton
Copy link
Member

@thestinger with rust-lang/rfcs#439 we'll have a trait definition that looks like:

pub trait Ord<Rhs = Self>: Eq<Rhs> + PartialOrd<Rhs> {
    fn cmp(&self, other: &Rhs) -> Ordering;
}

in which case I think we can leverage the Rhs type parameter to compare any type to the key type:

impl<K, V> TreeMap<K, V> {
    fn get<Q>(&self, q: &Q) -> Option<&V> where K: Ord<Q> { /* ... */ }
}

impl<T, U: BorrowFrom<T>> Ord<U> for T { /* ... */ }

I'm not entirely sure how much coherence would disallow, but it seems like it should be possible.

@aturon
Copy link
Member Author

aturon commented Nov 14, 2014

@pythonesque yes, you're right, supporting DerefMut here does not fit will with our general strategy of making things like clone explicit. (Deref is fine.) I will revise.

@aturon
Copy link
Member Author

aturon commented Nov 14, 2014

@pythonesque I've removed the DerefMut impl.

@thestinger I reinstated the find_with methods as #[experimental]; I have no objection to keeping them around under that status for now.

@alexcrichton re-r?

@thestinger
Copy link
Contributor

@aturon: Thanks, I'm in full agreement with this pull request then.

/// A generalization of Clone to borrowed data.
pub trait ToOwned<Owned> for Sized?: BorrowFrom<Owned> {
/// Create owned data from borrowed data, usually by copying.
fn to_owned(&self) -> Owned;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why to_owned must takes a reference?

I think that HashMap::entry() can take a ToOwned<K> as key, and create a owned object if needed. But to have that to work, we should have to_owned taking self to allow T: ToOwned<T> to just move.

Example: http://is.gd/YORUei

I didn't tried to modify this pull request with this change, so maybe there is problem in Cow with this change.

Following [the collections reform
RFC](rust-lang/rfcs#235),
this commit adds a new `borrow` module to libcore.

The module contains traits for borrowing data (`BorrowFrom` and
`BorrowFromMut`),
generalized cloning (`ToOwned`), and a clone-on-write smartpointer (`Cow`).
This commit generalizes methods like `get` and `remove` for `TreeMap`
and `TreeSet` to use the new `std::borrow` infrastructure.

[breaking-change]
This commit deprecates the `_equiv` family of methods on `HashMap` and
`HashSet` by instead generalizing the "normal" methods like `get` and
`remove` to use the new `std::borrow` infrastructure.

[breaking-change]
This commit handles the fallout from deprecating `_with` and `_equiv` methods.
Generalizes the BTree-based collections to use the new BorrowFrom
infrastructure for more flexible lookups and removals.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.