Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I not destructure this tuple when iterating over a HashMap?

Tags:

rust

I'm just learning rust and am working on an easy /r/dailyprogrammer task. Here's some code:

type ToDoList = HashMap<String, bool>;

fn print(list: &ToDoList) {
    let mut max_len: usize = 0;
    for (item, _) in list.iter() {
        max_len = max(max_len, item.len());
    }
    let end = format!("+---{}-+", 
        iter::repeat("-").take(max_len).collect::<String>());

    println!("{}", end);
    for (item, done) in list.iter() {
        let line = format!("| {0} {1}{2} |", 
            if done {"☑"} else {"☐"}, 
            item,
            iter::repeat("-")
                .take(max_len - item.len())
                .collect::<String>()
        );
        println!("{:?}", (item, done));
    }
    println!("{}", end);
}

I'm getting this error from rustc:

error: type mismatch resolving `<std::collections::hash::map::Iter<'_,
 collections::string::String, bool> as core::iter::Iterator>::Item ==
 (_, bool)`:  expected &-ptr,
     found bool [E0271]
 todolist.rs:19     for (item, done) in list.iter() {
 todolist.rs:20         let line = format!("| {0} {1}{2} |", 
 todolist.rs:21             if done {"☑"} else {"☐"},  
 todolist.rs:22             item,
 todolist.rs:23             iter::repeat("-")
 todolist.rs:24                 .take(max_len - item.len())
                ...
 todolist.rs:24:21: 24:31 error: the type of this value must be known in this context
 todolist.rs:24                 .take(max_len - item.len())
                                                ^~~~~~~~~~ note: in expansion of format_args! <std macros>:2:26: 2:57 note: expansion site <std
 macros>:1:1: 2:61 note: in expansion of format!
 todolist.rs:20:14: 26:4 note: expansion site error: aborting due to 2 previous errors

It seems like both of these are related to the same issue that somehow calling list.iter() is trying to give me a tuple of (_, String, bool) instead of just (String, bool). Why is that happening?

like image 218
Jon Cohen Avatar asked Jun 17 '15 05:06

Jon Cohen


2 Answers

The error message is not very readable. What is happening is that done is of type &bool since you are iterating in a non-owning way.

type mismatch resolving <std::collections::hash::map::Iter<'_, collections::string::String, bool> as core::iter::Iterator>::Item == (_, bool):

Basically you need to check what the actual type of std::collections::hash::map::Iter::Item is. As you can see in the docs it is (&'a K, &'a V).

Changing

for (item, done) in list.iter() {

to

for (item, &done) in list.iter() {

will fix your issue.

What causes this confusion, is Rust's type inference. Since you are using done as the argument to an if, Rust knows for a fact that it needs to be of type bool. So it goes backwards from there to the assignment of the done-binding until it finds some other concrete type. In other languages it would probably have been the other way around, and the error would have occurred in the if-condition.


As a side-note, in your first iteration for (item, _) in list.iter() { you are only interested in the keys of the HashMap. You can use for item in list.keys() for a more concise loop.

like image 140
oli_obk Avatar answered Oct 05 '22 15:10

oli_obk


It seems like both of these are related to the same issue that somehow calling list.iter() is trying to give me a tuple of (_, String, bool) instead of just (String, bool). Why is that happening?

You are right that both are related to the original error, but wrong about the error:

(_, bool)`:  expected &-ptr,

You get a (_, bool) tuple whereas the compiler expected to see a tuple of references (&_, &bool) to something. This _ could well be a String (or &String) in the compiler message, so this not the issue.

The issue is that you are expecting a value where the compiler expects a reference, this stems from the fact that iter() returns references to the underlying elements of the collection iterated upon and the simple fix is to change how you match:

for (item, &done) in &list {
}

As can be seen in the docs:

impl<'a, K, V> Iterator for Iter<'a, K, V>
type Item = (&'a K, &'a V)

The other way around would be to bind done to &bool and then dereference it before use:

for (item, done) in &list {
    if *done { ... } else { ... }
}
like image 26
Matthieu M. Avatar answered Oct 05 '22 14:10

Matthieu M.