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

BTree: remove Ord bound where it is absent elsewhere #79245

Merged
merged 1 commit into from
Feb 8, 2021

Conversation

ssomers
Copy link
Contributor

@ssomers ssomers commented Nov 20, 2020

Some btree methods don't really need an Ord bound and don't have one, while some methods that more obviously don't need it, do have one.

An example of the former is iter, even though it explicitly exposes the work of the Ord implementation ("sorted by key" - but I'm not suggesting it should have the Ord bound). An example of the latter is new, which doesn't involve any keys whatsoever.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 20, 2020
@jyn514
Copy link
Member

jyn514 commented Nov 21, 2020

I think this should have a reviewer from T-libs.

r? @dtolnay , maybe?

@jyn514 jyn514 added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. A-collections Area: std::collections. labels Nov 21, 2020
@scottmcm scottmcm added the needs-fcp This change is insta-stable, so needs a completed FCP to proceed. label Nov 22, 2020
@bors

This comment has been minimized.

@ssomers
Copy link
Contributor Author

ssomers commented Nov 24, 2020

@rustbot modify labels: +S-waiting-on-review -S-waiting-on-author

@crlf0710 crlf0710 added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 11, 2020
@dtolnay
Copy link
Member

dtolnay commented Dec 14, 2020

@rust-lang/libs:
@rfcbot fcp merge

This affects the following individual stable APIs. There are four kinds:

  • Functions in which no comparison can ever take place because no elements exist to compare. new(), default()
  • Functions that have no need to do comparisons. map.clear(), entry.key()
  • Functions where all comparison takes place not in terms of K, but some other type Q for which K: Borrow<Q>. We only ever compare by borrowing from K and comparing using Q: Ord.
  • Functions that just construct an iterator without doing work, but there remains an Ord bound on the Iterator impl. symmetric_difference, union
  impl<K, V> BTreeMap<K, V> {
      pub const fn new() -> BTreeMap<K, V>
-     where
-         K: Ord;

      pub fn clear(&mut self)
-     where
-         K: Ord;

      pub fn get<Q>(&self, key: &Q) -> Option<&V>
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn get_key_value<Q>(&self, k: &Q) -> Option<(&K, &V)>
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn contains_key<Q>(&self, key: &Q) -> bool
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn remove_entry<Q>(&mut self, key: &Q) -> Option<(K, V)>
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn range<T, R>(&self, range: R) -> Range<'_, K, V>
      where
-         K: Ord + Borrow<T>,
+         K: Borrow<T>,
          T: ?Sized + Ord,
          R: RangeBounds<T>;

      pub fn range_mut<T, R>(&mut self, range: R) -> RangeMut<'_, K, V>
      where
-         K: Ord + Borrow<T>,
+         K: Borrow<T>,
          T: ?Sized + Ord,
          R: RangeBounds<T>;

      pub fn split_off<Q>(&mut self, key: &Q) -> Self
      where
-         K: Ord + Borrow<Q>,
+         K: Borrow<Q>,
          Q: ?Sized + Ord;
}

  impl<K, V> Default for BTreeMap<K, V>
- where
-     K: Ord;

  impl<K, V> Debug for Entry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug,
      V: Debug;

  impl<K, V> Debug for VacantEntry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug;

  impl<K, V> Debug for OccupiedEntry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug,
      V: Debug;

  impl<'a, K, V> Entry<'a, K, V> {
      pub fn or_insert(self, default: V) -> &'a mut V
-     where
-         K: Ord;

      pub fn or_insert_with<F>(self, default: F) -> &'a mut V
      where
-         K: Ord,
          F: FnOnce() -> V;

      pub fn key(&self) -> &K
-     where
-         K: Ord;

      pub fn and_modify<F>(self, f: F) -> Self
      where
-         K: Ord,
          F: FnOnce(&mut V);

      pub fn or_default(self) -> &'a mut V
      where
-         K: Ord,
          V: Default;
  }

  impl<'a, K, V> VacantEntry<'a, K, V> {
      pub fn key(&self) -> &K
-     where
-         K: Ord;

      pub fn into_key(self) -> K
-     where
-         K: Ord;

      pub fn insert(self, value: V) -> &'a mut V
-     where
-         K: Ord;
  }

  impl<'a, K, V> OccupiedEntry<'a, K, V> {
      pub fn key(&self) -> &K
-     where
-         K: Ord;

      pub fn remove_entry(self) -> (K, V)
-     where
-         K: Ord;

      pub fn get(&self) -> &V
-     where
-         K: Ord;

      pub fn get_mut(&mut self) -> &mut V
-     where
-         K: Ord;

      pub fn into_mut(self) -> &'a mut V
-     where
-         K: Ord;

      pub fn insert(&mut self, value: V) -> V
-     where
-         K: Ord;

      pub fn remove(self) -> V
-     where
-         K: Ord;
  }

  impl<T> BTreeSet<T> {
      pub const fn new() -> BTreeSet<T>
-     where
-         T: Ord;

      pub fn range<K, R>(&self, range: R) -> Range<'_, T>
      where
-         T: Ord + Borrow<K>,
+         T: Borrow<K>,
          K: ?Sized + Ord,
          R: RangeBounds<K>;

      pub fn symmetric_difference<'a>(&'a self, other: &'a BTreeSet<T>) -> SymmetricDifference<'a, T>
-     where
-         T: Ord;

      pub fn union<'a>(&'a self, other: &'a BTreeSet<T>) -> Union<'a, T>
-     where
-         T: Ord;

      pub fn clear(&mut self)
-     where
-         T: Ord;

      pub fn contains<Q>(&self, value: &Q) -> bool
      where
+         T: Ord + Borrow<Q>,
-         T: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn remove<Q>(&mut self, value: &Q) -> bool
      where
-         T: Ord + Borrow<Q>,
+         T: Borrow<Q>,
          Q: ?Sized + Ord;

      pub fn split_off<Q>(&mut self, key: &Q) -> Self
      where
-         T: Ord + Borrow<Q>,
+         T: Borrow<Q>,
          Q: ?Sized + Ord;
  }

  impl<T> Default for BTreeSet<T>
- where
-     T: Ord;

@rfcbot
Copy link

rfcbot commented Dec 14, 2020

Team member @dtolnay has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Dec 14, 2020
Copy link
Member

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

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

I would request to keep the Ord bound on all Entry methods that mutate the collection, and on symmetric_difference/union.

@ssomers
Copy link
Contributor Author

ssomers commented Dec 14, 2020

I would request to keep the Ord bound on all Entry methods that mutate the collection

Does that include the methods that do not actually mutate but allow mutation, i.e., OccupiedEntry::get_mut and into_mut? If so, I can't imagine why plain get is allowed to give up its Ord bound.

@ssomers
Copy link
Contributor Author

ssomers commented Dec 14, 2020

I would request to keep the Ord bound […] on symmetric_difference/union.

Pointing out that inconsistency in the initial form of this PR, answers my implicit question: why impose unused bounds anywhere? It allows tuning the implementation later.

Another inconsistency is that BTreeMap::get loses the bound, but BTreeSet::get doesn't, and can't as long as it reuses an implementation of Recover. So I'm motivated to turn away from touching those.

That leaves version 2 with this set of changes:

  impl<K, V> BTreeMap<K, V> {
      pub const fn new() -> BTreeMap<K, V>
-     where
-         K: Ord;

      pub fn clear(&mut self)
-     where
-         K: Ord;
  }

  impl<K, V> Default for BTreeMap<K, V>
- where
-     K: Ord;
  }

  impl<K, V> Debug for Entry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug,
      V: Debug;
  }

  impl<K, V> Debug for VacantEntry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug;
  }

  impl<K, V> Debug for OccupiedEntry<'_, K, V>
  where
-     K: Ord + Debug,
+     K: Debug,
      V: Debug;
  }

  impl<'a, K, V> Entry<'a, K, V> {
      pub fn key(&self) -> &K
-     where
-         K: Ord;
  }

  impl<'a, K, V> VacantEntry<'a, K, V> {
      pub fn key(&self) -> &K
-     where
-         K: Ord;

      pub fn into_key(self) -> K
-     where
-         K: Ord;
  }

  impl<'a, K, V> OccupiedEntry<'a, K, V> {
      pub fn key(&self) -> &K
-     where
-         K: Ord;

      pub fn get(&self) -> &V
-     where
-         K: Ord;

      pub fn get_mut(&mut self) -> &mut V
-     where
-         K: Ord;

      pub fn into_mut(self) -> &'a mut V
-     where
-         K: Ord;
  }

  impl<T> BTreeSet<T> {
      pub const fn new() -> BTreeSet<T>
-     where
-         T: Ord;

      pub fn clear(&mut self)
-     where
-         T: Ord;
  }

  impl<T> Default for BTreeSet<T>
- where
-     T: Ord;

@BurntSushi
Copy link
Member

Can someone say why we would want to do this, other than minor simplifications in the type signature? Is there a concrete benefit to this? Conversely, if we do this and accidentally remove Ord from an API that wants it in the future, we're stuck. (The APIs proposed look fine to me, so maybe that's a weak argument, but it still seems like a risk to me.)

@ssomers
Copy link
Contributor Author

ssomers commented Dec 14, 2020

Is there a concrete benefit to this?

  • Consistency in the current API: I've read posts, and long ago experienced myself, stumbling upon the bound on new while making code generic over types of containers (e.g. when comparing BTreeSet vs. HashSet). Though I can't find any concrete traces of that.
  • Consistency in the future API. Methods like drain_filter and first_entry impose an Ord bound just because it seems popular somehow, but their behaviour is close to iter which doesn't. The recent into_keys and into_values impose an Ord bound, while into_iter doesn't. I'm sure explicitly listing the Ord bound everywhere would help realizing that, but the second version doesn't do that anymore.
  • In implementations in btree, the inability to call new or default requires us to rewrite it sometimes, though I'm surprised to only find one occurrence that made it to master.

@BurntSushi
Copy link
Member

Thanks for explaining! I'm a little skeptical that those things are worth the risk here personally. The last bullet point (writing generic code) seems the most compelling to me I think, but I'm having a hard time of thinking of how that actually happens in practice.

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Dec 15, 2020
…ulacrum

BTreeSet: simplify implementation of pop_first/pop_last

…and stop it interfering in rust-lang#79245.
r? `@Mark-Simulacrum`
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Dec 15, 2020
…ulacrum

BTreeSet: simplify implementation of pop_first/pop_last

…and stop it interfering in rust-lang#79245.
r? ``@Mark-Simulacrum``
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this pull request Dec 15, 2020
…ulacrum

BTreeSet: simplify implementation of pop_first/pop_last

…and stop it interfering in rust-lang#79245.
r? ```@Mark-Simulacrum```
@dtolnay
Copy link
Member

dtolnay commented Jan 31, 2021

Could someone make an updated list of every API change, like #79245 (comment)? This PR is difficult to review otherwise because all the API changes are taking place in lines of code that are not touched.

@dtolnay
Copy link
Member

dtolnay commented Jan 31, 2021

For review it might even be better to do this as 2 PRs, one moving Ord bounds from impl blocks to functions (no API change) and then an API changing PR that only removes bounds.

@ssomers
Copy link
Contributor Author

ssomers commented Jan 31, 2021

I've isolated the first commit as #81610 and will rebase here if and when it gets merged.
@rustbot modify labels: +S-waiting-on-author -S-waiting-on-review

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 31, 2021
@ssomers ssomers changed the title BTreeMap: remove Ord bound where it will never be needed BTree: remove Ord bound where it is absent elsewhere Feb 6, 2021
@ssomers
Copy link
Contributor Author

ssomers commented Feb 6, 2021

I backed out on all changes in Entry because the "everything Ord" rule applies there.
@rustbot modify labels: -S-waiting-on-author +S-waiting-on-review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 6, 2021
@dtolnay
Copy link
Member

dtolnay commented Feb 8, 2021

This is a subset of the changes from the prior completed FCP.

  impl<K, V> BTreeMap<K, V> {
      pub fn clear(&mut self)
-     where
-         K: Ord;
  }

  impl<T> BTreeSet<T> {
      pub fn clear(&mut self)
-     where
-         T: Ord;
  }

and a few unstable ones (tracking issue #75294):

  impl<K, V> BTreeMap<K, V> {
      pub fn into_keys(self) -> IntoKeys<K, V>
-     where
-         K: Ord;
- 
      pub fn into_values(self) -> IntoValues<K, V>
-     where
-         K: Ord;
  }

@dtolnay
Copy link
Member

dtolnay commented Feb 8, 2021

@bors r+

@bors
Copy link
Contributor

bors commented Feb 8, 2021

📌 Commit 9066c73 has been approved by dtolnay

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 8, 2021
@bors
Copy link
Contributor

bors commented Feb 8, 2021

⌛ Testing commit 9066c73 with merge 0b96f60...

@bors
Copy link
Contributor

bors commented Feb 8, 2021

☀️ Test successful - checks-actions
Approved by: dtolnay
Pushing 0b96f60 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Feb 8, 2021
@bors bors merged commit 0b96f60 into rust-lang:master Feb 8, 2021
@rustbot rustbot added this to the 1.52.0 milestone Feb 8, 2021
@ssomers ssomers deleted the btree_curb_ord_bound branch February 8, 2021 11:31
@pthariensflame
Copy link
Contributor

relnotes needed here?

@ssomers
Copy link
Contributor Author

ssomers commented Apr 6, 2021

I wouldn't know the answer even if I understood the question, but as a summary: the only change to the public stable API is that BTreeMap::clear and BTreeSet::clear no longer have the Ord bound.

m-ou-se added a commit to m-ou-se/rust that referenced this pull request Sep 1, 2021
BTree: remove Ord bound from new

`K: Ord` bound is unnecessary on `BTree{Map,Set}::new` and their `Default` impl. No elements exist so there are nothing to compare anyway, so I don't think "future proof" would be a blocker here. This is analogous to `HashMap::new` not having a `K: Eq + Hash` bound.

rust-lang#79245 originally does this and for some reason drops the change to `new` and `Default`. I can see why changes to other methods like `entry` or `symmetric_difference` need to be careful but I couldn't find out any reason not to do it on `new`.

Removing the bound also makes the stabilisation of `const fn new` not depending on const trait bounds.

cc `@steffahn` who suggests me to make this PR.

r? `@dtolnay`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-collections Area: std::collections. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. merged-by-bors This PR was explicitly merged by bors. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.