1

I am new to Rust and recently started learning. I wrote a simple program which does the following.

  1. Read a text file
  2. Split the words and store them in a HashMap with their occurrence count
  3. Iterate over the map and print the word and occurrence count
use std::io;
use std::env;
use std::collections::HashMap;
use std::fs;

fn main() {
    let path = env::args().nth(1).unwrap();
    let contents = read_file(path);
    let map = count_words(contents);
    print(map);
}

fn read_file(path: String) -> (String){
    let contents =  fs::read_to_string(path).unwrap();
    return contents;
}

fn count_words(contents: String) -> (HashMap<String, i32>){
    let split = contents.split(&[' ', '\n'][..]);
    let mut map = HashMap::new();
    for w in split{
        if map.get(w) == None{
            map.insert(w.to_owned(), 1);
        }
        let count = map.get(w);
        map.insert(w.to_owned(), count + 1); // error here
    }
    return map;
}

fn print(map: HashMap<String, i32>){
    println!("Occurences..");

    for (key, value) in map.iter() {
        println!("{key}: {value}");
    }
}

I am able to read the file and add the words into a HashMap and print. However, while trying to add or increment, I get below error.

error[E0369]: cannot add {integer} to Option<&{integer}> --> src\main.rs:27:40 | 27 | map.insert(w.to_owned(), count + 1); | ----- ^ - {integer} |
| | Option<&{integer}>

I know this approach should work in other languages like Java, C# etc. but unsure about how it should work in Rust.

Souvik Ghosh
  • 4,456
  • 13
  • 56
  • 78
  • Does https://stackoverflow.com/questions/28512394/how-to-lookup-from-and-insert-into-a-hashmap-efficiently answer your question? – Matthieu M. Sep 24 '22 at 12:40
  • You should use pattern matching in order to get from an `Option<{integer}>` to an `{integer}`. – jthulhu Sep 24 '22 at 12:53
  • Does this answer your question? [How to lookup from and insert into a HashMap efficiently?](https://stackoverflow.com/questions/28512394/how-to-lookup-from-and-insert-into-a-hashmap-efficiently) – Jmb Sep 24 '22 at 17:01

1 Answers1

4

In this block:

if map.get(w) == None{
            map.insert(w.to_owned(), 1);
        }
        let count = map.get(w);
        map.insert(w.to_owned(), count + 1); // error here

map.get(w) gives you an Option<w> (doc);

You seem to know this to some extent, as you check if it's a None earlier - the other possibility is that it is what you want - a Some(w) (not just w);

You cannot add an int to a Some, as the compiler tells you - you have to take "the w" (the count integer) out of the Some.


You can unwrap (doc), though it is not advisable.


You can use pattern matching:

match map.get(&w) {
    Some(count) => { map.insert(w, count + 1); }
    None => { map.insert(w, 1); }
}

Or, written differently:

if let Some(count) = map.get(&w) {
    map.insert(w, count + 1);
} else {
    map.insert(w, 1);
};

Or, better yet, you can use the Entry API, which turns it into a simple one liner:

    *map.entry(w.to_owned()).or_default() += 1;
Gremious
  • 304
  • 3
  • 10
  • The if-else check for Some worked but the one line threw an error '^^^ expected struct `String`, found `&str`' in the return map statement – Souvik Ghosh Sep 24 '22 at 14:41
  • Edited your one liner with some correction and now it's working. – Souvik Ghosh Sep 24 '22 at 14:51
  • @SouvikGhosh Just like inserting into a map wants an owned String, so does the `.entry()` call, yes, as it will potentially also insert. Though you edited out `.or_default()` for `.or_insert(0)`, which are equivalent. A default on a number type would be 0. I personally prefer `or_default()`, as I think it is relatively obvious what is happening, but if you want to be verbose - not a fault. – Gremious Sep 24 '22 at 18:32
  • Approaches with `match` and `if let Some...` probably will not work because of: cannot borrow `map` as mutable because it is also borrowed as immutable – CAMOBAP Dec 13 '22 at 09:14