Add or_try_*
variants for HashMap and BTreeMap Entry APIs · Issue #336 · rust-lang/libs-team (original) (raw)
Proposal
Problem statement
The HashMap
and BTreeMap
entry APIs make it possible to operate on an item that is either already in a map or on a newly inserted item if one wasn't yet present, while only needing a single lookup.
The limitation with the current API is that methods like or_insert_with
do not allow for item construction that can fail.
Motivating examples or use cases
I found several cases when moving to work on an existing codebase, where code was commented to explain why this had to be manually implemented instead of using the Entry
API. The examples there are a little complex though, and I'll show some simpler ones.
Without the proposed new methods, it is either verbose:
fn main() -> Result<(), std::num::ParseIntError> { let mut map: HashMap<&str, usize> = HashMap::new(); let value = "42";
match map.entry("poneyland") {
Entry::Occupied(mut entry) => {
*entry.get_mut() += 1;
}
Entry::Vacant(entry) => {
entry.insert(value.parse()?);
}
};
assert_eq!(map["poneyland"], 42);
Ok(())
}
Or inefficient (multiple hashmap lookups):
fn main() -> Result<(), std::num::ParseIntError> { let mut map: HashMap<&str, usize> = HashMap::new(); let value = "42";
if let Some(value) = map.get_mut("poneyland") {
*value+= 1;
} else {
map.insert("poneyland", value.parse()?);
}
assert_eq!(map["poneyland"], 42);
Ok(())
}
With the proposed changes:
fn main() -> Result<(), std::num::ParseIntError> { let mut map: HashMap<&str, usize> = HashMap::new(); let value = "42";
map.entry("poneyland")
.and_modify(|entry| *entry += 1)
.or_try_insert_with(|| value.parse())?;
assert_eq!(map["poneyland"], 42);
Ok(())
}
Solution sketch
The method signatures look like this:
impl Entry<K, V> {
pub fn or_try_insert_with<F: FnOnce() -> Result<V, E>, E>(
self,
default: F,
) -> Result<&'a mut V, E>;
pub fn or_try_insert_with_key<F: FnOnce(&K) -> Result<V, E>, E>(
self,
default: F,
) -> Result<&'a mut V, E>;
}
I have complete implementations in this PR: https://github.com/rust-lang/rust/pull/120708/files
Alternatives
It is already possible to implement these methods by explicitly matching on the the Entry
enum variants (see motivating examples), but this is far less ergonomic than the proposed methods. Similarly, or_insert_with
could be implemented by users themselves, but it is included in the standard library because it is more streamlined than an explicit match expression.
A third party crate could also offer the same functionality using an extension trait. It's hard to imagine a crate being successful with only this feature though, though it could be appropriate in a "grab-bag" utility crate.
Finally, it's possible to use the existing Entry
methods if you handle errors by panicking.
Links and related work
An implementation for HashMap's entry API:
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.