Open cyqsimon opened 1 year ago
for the types with mutable members (so not sets) imho the returned reference should be a mutable reference, the caller can easily convert to a shared reference if they desire but not the other way around.
What is here that cannot already be done with the safe entry API (although it's missing for sets)?
let mut set: HashMap<String, ()>;
set.entry(String::from("abc")).insert_entry(()).get()
for the types with mutable members (so not sets) imho the returned reference should be a mutable reference, the caller can easily convert to a shared reference if they desire but not the other way around.
I certainly see the "why not?" appeal here, and TBH I did consider this while writing the post. Ultimately I think it's a tradeoff between consistency and functionality. It might be surprising for some users to find that:
*_get
(as opposed to *_get_mut
) returns a mutable reference*Set
don't return mutable references when that's the case for all other collectionsUltimately I decided to go for the consistency and just return an immutable reference regardless. We can always add *_get_mut
variants in the future if necessary, at least that's my thinking.
But yeah admittedly I am pretty torn on what to do here. I would like to know the various opinions of the broader community.
What is here that cannot already be done with the safe entry API (although it's missing for sets)?
I'm entirely in agreement here that in the specific case of *Map
types (and in the future hopefully, *Set
types as well; in fact if I'm not mistaken, it has already landed in hashbrown
, but hasn't propagated to the standard library yet), these methods will offer little except for ergonomic improvements over the more general entry API. However for other collection types that don't have an entry API (or even the concept of it), they will be somewhat more tangibly useful IMO.
So again it comes down to a tradeoff with consistency I suppose. It would be nice if this family of methods is universal across all collections; but I also fully admit that this will come with the downside of duplication of functionality. Honestly, I don't know which path forward is better either. After all, that's why this is a pre-RFC, sorry ¯\_(ツ)_/¯
The proposed functions are unfortunately not all that practical because they cause the underlying collection to be borrowed mutably, meaning that one cannot call any function or get any other reference out of them.
let new = vec.push_get(123);
// this errors:
println!("{}", vec[1], new);
The proposed functions are unfortunately not all that practical because they cause the underlying collection to be borrowed mutably, meaning that one cannot call any function or get any other reference out of them.
Yikes. That's a very good point (unfortunately) that I overlooked. I just assumed that since we're only retaining an immutable reference, the borrow checker would see that the collection is no longer being borrowed mutably.
Speaking of which, I'm very curious whether there is actually a safety reason behind this behaviour, or is it just a technical limitation: why doesn't the borrow checker see that the retained reference has been "downgraded"? It seems to me like all the information the borrow checker would need to deduce this is available in the function signature.
Speaking of which, I'm very curious whether there is actually a safety reason behind this behaviour, or is it just a technical limitation: why doesn't the borrow checker see that the retained reference has been "downgraded"? It seems to me like all the information the borrow checker would need to deduce this is available in the function signature.
A simple counterexample:
fn demut(x: &mut AtomicU32) -> &u32 {
&*x.get_mut()
}
let mut x = AtomicU32::new(1);
let y = demut(&mut x);
// y is now an immutable &u32 that equals &1
x.store(2, SeqCst);
// is *y now 1 or 2?
@cyqsimon As @SOF3 's example shows, the borrow checker can't see that because that would be essentially an API guarantee, not an implementation detail. Besides, there's the "design rule" that the borrow checker can't depend on information from inside of function bodies, as those are considered implementation details too. There has been some ideas of having a "downgrading annotation" (e.g. https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256/21), but no concrete proposals, and I doubt the time is ripe for such a proposal at the moment.
I ran across this issue today when I'm attempting to write a function like this:
To my surprise, there is no easy way to do this with
HashSet
, or for that matter, with any collection. I ended up having to write this:This is obviously bad for a multitude of reasons:
Player::clone
callHashset::get
callunwrap
So I asked on the Rust-lang discord guild here, and Yand.rs gave this solution:
... but also had this to say:
My proposition
Add a family of associated functions that provide this capability to all collections:
Potential questions
*_get_mut
variants too?VecDeque
andLinkedList
already havefront
andback
methods. Do they makepush_front_get
andpush_back_get
redundant?(Option<V>, &V)
instead)(&K, &V, Option<V>)
or((&K, &V), Option<V>)
)