We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 20 of 24
Count words with the HashMap entry API
Repair the supplied bump helper using HashMap's entry API. Keep both signatures and use bump when counting words:
pub fn word_counts(words: &[String]) -> Vec<(String, i64)>
fn bump<'m>(map: &'m mut HashMap<String, i64>, key: &str) -> &'m mut i64
word_counts returns one pair for each distinct input string. Sort by count descending, then by key ascending using Rust's String ordering. This is lexicographic UTF-8 byte order, not locale-specific dictionary order.
Compare entire strings exactly. Do not split, trim, lowercase or normalize them. Empty strings and strings containing spaces are valid keys. Preserve the input slice and its strings. Empty input returns an empty vector.
["b", "a", "b"] -> [("b", 2), ("a", 1)]
["z", "y", "z", "y", "q"] -> [("y", 2), ("z", 2), ("q", 1)]
["Ap", "ap", "Ap"] -> [("Ap", 2), ("ap", 1)]
["", "a b", ""] -> [("", 2), ("a b", 1)]
Despite its name, bump does not increment the counter itself. It returns a mutable reference to the existing value, or inserts zero and returns a reference to that zero. word_counts increments the returned counter once per input string. Keep that division of responsibility.
Why the starter is rejected
The starter calls get_mut and returns its reference on the Some branch. On the None branch it tries to insert and borrow the map again. With the checked Rust 1.98.1 toolchain, this produces E0499: the later mutable borrow conflicts with the borrow associated with the returned reference.
The intended branches are mutually exclusive, but this form asks the borrow checker to distinguish a returned borrow on one path from a new borrow on another. Its analysis rejects the pattern. That is a limitation of the accepted program forms, not evidence that every lookup followed by an insert is inherently unsafe. Compiler acceptance can change; this exercise teaches an API that directly represents the operation you need.
Use an entry to handle both cases
entry returns a handle to an occupied or vacant entry while borrowing the map mutably. or_insert supplies the value only when vacant and returns a mutable reference to the stored value in either case.
Here is a complete example:
use std::collections::HashMap;
fn counter<'m>(map: &'m mut HashMap<String, i64>, key: &str) -> &'m mut i64 {
map.entry(key.to_string()).or_insert(0)
}
fn main() {
let mut map = HashMap::new();
assert_eq!(*counter(&mut map, "tea"), 0);
*counter(&mut map, "tea") += 2;
assert_eq!(*counter(&mut map, "tea"), 2);
assert_eq!(map.len(), 1);
}
The returned reference is tied to the mutable borrow of map, not to key. The map owns its String key. While the returned reference is still used, you cannot independently mutate the map through another borrow.
This avoids writing a separate get_mut/insert/get_mut sequence. It is not a promise about an exact number of hash computations or the fastest possible implementation. The expression key.to_string() is evaluated even when the entry already exists, so an occupied entry does not avoid constructing an owned key. A borrowed-key lookup has different costs; compare designs if that matters in application code.
Defaults and counting are separate choices
or_insert evaluates its default argument before the call. For the literal zero, that is appropriate. or_insert_with defers a closure's body until a vacant entry needs a value, and or_default uses the value type's Default. An empty Vec does not allocate element storage merely by being constructed; choose lazy defaults for their actual work or side effects.
The common chain and_modify(|v| *v += 1).or_insert(1) can count directly,
but do not substitute it into bump while retaining the caller's increment:
that would increment twice. This exercise's helper must retain its specified
zero-or-existing-value behavior.
See the HashMap entry documentation for the available entry operations.
Sort the result explicitly
HashMap iteration order is arbitrary. The same order may appear in repeated runs, but the API does not promise it. Sorting only by count leaves ties in an unspecified order; the key comparison resolves those ties.
The supplied comparator follows the contract:
b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)). It compares counts backwards,
then keys forwards when the counts match. Keep both parts.
Clippy's map_entry lint can identify some contains_key/insert patterns, but it does not prove that a counting algorithm or its ordering is correct. Passing requires the expected pairs, successful compilation and Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.