Entry and_modify for multiple values

Hi folks, fairly new to rust and still learning. I checked through the forum and std docs, but didn't see an example of any uses of and_modify on Entry for a multi-value variable. I specifically was working with HashMap's and really enjoyed the simplicity of the syntax:

my_map.entry(key_ref).and_modify(|my_val| val += 1).or_insert(1);

But when starting to create a hash map with tuple values I couldn't figure out how to modify two values in one line i.e.: :

use std::collections::HashMap;
let my_map: HashMap<char, (i32, i32)> = HashMap::new();
my_map.insert('c', (1, 1));
let my_key: char = 'c';
my_map.entry(my_key).and_modify(|my_val| my_val.0 += 1, my_val.1 *= 2).or_insert((1, 1));

or:

my_map.entry(my_key).and_modify(|(my_val1, my_val2)| *my_val1 += 1, *my_val2 *= 2).or_insert((1, 1));

both result in

error[E0061]: this method takes 1 argument but 2 arguments were supplied

while:

my_map.entry(my_key).and_modify(|my_val| (my_val.0 + 1, my_val.1 * 2)).or_insert((1, 1));

gives:

  = note: expected unit type `()`
                 found tuple `(i32, i32)`

Since I assume its looking for an in-place operation with null tuple returned.

If the one liner isn't idiomatic what's the rustacean's opinionated approach here? get/entry to a local variable, modify, then insert?

Hello. Please post the full error you're getting (what you see in the terminal when running cargo) so we can see it without trying to reproduce your problem separately. Rust compiler errors often contain clues for how they can be fixed. This will also tell us more about the types you're using, which you haven't shown.

post updated for clarity, thanks!

1 Like

and_modify gives its function a &mut V, so you need to write a mutation operation, not return a new value:

my_map.entry(key_ref)
    .and_modify(|my_val| *my_val = (my_val.0 + 1, my_val.1 * 2))
    .or_insert((1, 1));

However, in my opinion, .and_modify().or_insert() is a bad pattern:

  • When you genuinely need to do two different things depending on whether the entry is present, a match of the Entry enum is plain, simple code that does not require the reader to know what any of the specific Entry methods do.

  • When accumulating values, it requires you to specify what you're accumulating twice: once in the modify and once in the insert. Instead, you should use .or_insert() (or .or_insert_default() when applicable) only and then modify the reference you get. This is a little awkward in your case with the tuple since you need a variable, but simple in the simple case as your first sample:

    *my_map.entry(key_ref).or_insert(0) += 1;
    

    For the tuple:

    let my_val = my_map.entry(key_ref).or_insert((0, 1));
    *my_val = (my_val.0 + 1, my_val.1 * 2);
    

    This may not be a “one-liner”, but it is simpler code. That's much more important.

5 Likes

This code could work like this:

my_map.entry(my_key).and_modify(|(my_val1, my_val2)| {*my_val1 += 1; *my_val2 *= 2}).or_insert((1, 1));   

As you see i create a "code" block with the {} and have to use ; to finish a statement. With your code the compiler sees the , and thinks that now the next argument starts.

2 Likes