Lack of ergonomy when working with HashMap and numeric values


#1

Consider this simple code:

let map = HashMap::<String, usize>::new();
let data = Vec::<String>::new();
let translated: Vec<usize> = data.iter().map(|token| {
    map.get(token).unwrap_or(47)   
}).collect();

It does not compile due to two rather absurd errors:

  |
7 |         map.get(token).unwrap_or(47)   
  |                                  ^^
  |                                  |
  |                                  expected &usize, found integral variable
  |                                  help: consider borrowing here: `&47`
  |
  = note: expected type `&usize`
             found type `{integer}`

error[E0277]: the trait bound `std::vec::Vec<usize>: std::iter::FromIterator<&usize>` is not satisfied
 --> src/main.rs:8:8
  |
8 |     }).collect();
  |        ^^^^^^^ a collection of type `std::vec::Vec<usize>` cannot be built from an iterator over elements of type `&usize`
  |
  = help: the trait `std::iter::FromIterator<&usize>` is not implemented for `std::vec::Vec<usize>`

The solution is obvious (add some & and * here and there):

let map = HashMap::<String, usize>::new();
let data = Vec::<String>::new();
let translated: Vec<usize> = data.iter().map(|token| {
    *map.get(token).unwrap_or(&47)   
}).collect();

But this is totally ugly and annoying. Are there some plans in future to avoid this?
Or did I miss some part of HashMap API which avoids this?


#2

An alternative way to write

*map.get(token).unwrap_or(&47)

is

map.get(token).cloned().unwrap_or(47)

(well, slightly different semantics, but functionally equivalent for Copy types).

I’d call it more ergonomic since it avoids unecessary references, even though it’s slightly longer. I’m not sure if there’s any truly more ergonomic way to write it though.


#3

I don’t think there’s anything absurd about these at all. get is supposed to return references, because if it didn’t, it wouldn’t be useful for non-Copy types, which is most types in Rust.

Also, every container in Rust behaves this way.

Let’s say it was possible to change containers to return values instead of references for Copy types (which I don’t think you can do currently). Now you’ve got the problem of how a container’s API changing depending on the specific type being stored in it, which will complicate all generic code trying to use them.

Now, containers could get a new get_copy method… but as has already been noted, you can express that as .get(key).cloned(). Also, if you’re doing that enough to justify it, you could implement it on an extension trait… which is why I added stuff like get_copy, get_default, and get_mut_default to a container type I was using.

So, I’d agree it could be a little more convenient, but I wouldn’t call it “absurd” or “totally ugly and annoying”.


#4

I get the stuff about the function signatures.
My point was more about automatic dereferencing and borrowing.


#5

You mean allowing unwrap(47) instead of unwrap(&47)? I’m unconvinced that would be very well-received: knowing when borrowing is happening is an important part of Rust.

That said, I’m struggling to think of a case where allowing automatic borrowing of Copy types would actually present a problem…


#6

There has been some discussion around auto ref/deref for copy types, but it’s sort of controversial.


#7

A variety of pending experiments around this area are tracked in