Why is there a distinct impl section for std::collections::btree_map::Entry::or_default
like this:
impl<'a, K, V, A> Entry<'a, K, V, A>
where
K: Ord,
V: Default,
A: Allocator + Clone,
pub fn or_default(self) -> &'a mut V
when ( as I understand it ) a constraint on the function would do? I think it is probably just "random" and "meaningless", but perhaps there is some subtlety to this I don't understand.
( I came across this in my experiment in making my own (equivalent) BTreeMap, my version is here: Entry in btree_experiment - Rust like this:
pub fn or_default(self) -> &'a mut V
where
V: Default,
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q> + Ord,
Q: Ord + ?Sized,
I don't see why K needs to be Ord here. I also don't understand the ?Sized bound on Q ( but maybe there is a good reason for that - actually I think I do understand that now... ok, it allows Q to be unsized ).
I believe the K: Ord and Q: Ord bounds are meant to ensure that this requirement from the Borrow trait applies:
Further, when providing implementations for additional traits, it needs to be considered whether they should behave identically to those of the underlying type as a consequence of acting as a representation of that underlying type. Generic code typically uses Borrow<T> when it relies on the identical behavior of these additional trait implementations. These traits will likely appear as additional trait bounds.
In particular Eq, Ord and Hash must be equivalent for borrowed and owned values: x.borrow() == y.borrow() should give the same result as x == y.
The K: Ord bound isn’t very restrictive anyways, since you can’t insert items into the BTreeMap unless K: Ord.
This is particularly useful to allow slice types like str and [T], so you can pass an &str or &[T] to look up items in a BTreeMap<String, _> or BTreeMap<Vec<T>, _>.
If K: Ord is omitted, then (a) the BTreeMap must be empty, because there’s no way to insert into it, and (b) if for some reason it was not empty, navigating its entries by borrowing Q from them would not be guaranteed to have any relation to the way they are sorted in the tree.
I don't see what the K: Ord is for. Retain doesn't involve any comparisons. Unless somehow it allows the closure to do comparisons? But I think it can anyway. Hmm.
I agree. As far as I can tell, this K: Ord bound is unnecessary. But as before, it doesn’t make any practical difference because a populated BTreeMap<K, V> always has K: Ord.
Since it's come up a few times, I think it's worth pointing out that having or leaving off unnecessary bounds can make a practical difference in some cases. For example I could have a
and then implement methods such that a T variant is only populated if K: Ord + Eq, and an M variant only if K: Hash + Eq, et cetera. But if you require K: Ord + Eqeverywhere in your crate (in particular if you require them on the struct definition), then I'll have to include those bounds too -- which is an unwanted restriction given some consumer of my type that never needs to construct the T variant!
Similar thinking can apply to methods, perhaps especially when macros are involved.
On the flip side, adding a trait bound is a breaking change, so removing (or originally not having) the Ord bound somewhere is a promise that you will never require it, modulo a major SemVer bump. So "unnecessary" bounds can be a form of future-proofing.
(This is more sharply relevant to std, which operates under the assumption that there may never be a major version bump, and thus all such promises last forever.)
Ah, I am wondering if a possible (perhaps inefficient) implementation could repeatedly pop the first element from the BTree, then either discard it or push it into a temporary Vec (depending on the result of f), then at the end re-insert the temporary Vec pairs back into the tree. That would require K: Ord. So the bound is to not preclude this implementation possibility.
But still, the bound could be added when required, and not break anything. I think!