Cannot move out of a mutable reference iterator, fold, map

Hello,

I have a struct implementing an Iterator trait. However I cannot perform a fold and a map


fn from_html_to_map(html: &str) -> HashMap<String, String>  {
   

    let iterator = TagIterator::new(html);

    let mut init = Parser {
        map: HashMap::new(),
        character_start_position: None,
        tmp_char: "",
        code_start_position: None,
        tmp_codes: vec![],
    };

    let map = iterator
        .fold(&mut init, |parser, element| {
            

          // updating parser ...

           parser
        })
        .map;

        map
         
}

And I am getting the following error

error[E0507]: cannot move out of a mutable reference
  --> examples\html_special_chars_generator.rs:53:15
   |
53 |       let map = iterator
   |  _______________^
54 | |         .fold(&mut init, |parser, element| {
55 | |             match element {
56 | |                 Elements::Start(tag, _begin, end) => {
...  |
91 | |         })
92 | |         .map;
   | |____________^ move occurs because value has type `HashMap<String, String>`, which does not implement the `Copy` trait
   |
help: consider borrowing here
   |
53 |     let map = &iterator
54 |         .fold(&mut init, |parser, element| {
55 |             match element {
56 |                 Elements::Start(tag, _begin, end) => {
57 |                     if tag.name == String::from("td") && has_class(&tag, String::from("character"))
58 |                     {
 ...

Please advise.

Thank you

Did you mean to return init.map?

It's unclear from the example what fold() returns, but I'm guessing it returns a temporarily borrowed reference, and .map on that is also borrowed, rather than owned object. You can't give ownership of a borrowed reference — that's stealing!

Thank you @kornel for your reply.

I'll add more details

tag_iterator.rs file extract :


impl Iterator for TagIterator<'_> {
    type Item = Elements;

    fn next(&mut self) -> Option<Self::Item> {
       // ...
    }
}

main example file :


struct Parser<'a> {
    map: HashMap<String, String>,
    character_start_position: Option<usize>,
    tmp_char: &'a str,

    code_start_position: Option<usize>,
    tmp_codes: Vec<&'a str>,
}


fn from_html_to_map(html: &str) -> HashMap<String, String>  {
    let has_class = |tag: &Tag, expected_class_name: String| {
        if let Some(actual_class_name) = tag.classes() {
            return actual_class_name.contains(expected_class_name.as_str());
        }
        false
    };

    let iterator = TagIterator::new(html);

    let mut init = Parser {
        map: HashMap::new(),
        character_start_position: None,
        tmp_char: "",
        code_start_position: None,
        tmp_codes: vec![],
    };

    let map = iterator
        .fold(&mut init, |parser, element| {
            match element {
                Elements::Start(tag, _begin, end) => {
                    if tag.name == String::from("td") && has_class(&tag, String::from("character"))
                    {
                        parser.character_start_position = Some(end);
                    }
                    if tag.name == String::from("code") {
                        parser.code_start_position = Some(end);
                    }
                }
                Elements::End(name, begin, _end) => {
                    if name == String::from("td") && parser.character_start_position.is_some() {
                        let character = html.get(parser.character_start_position.unwrap()..begin);
                        //println!("- {:?}", character);
                        parser.character_start_position = None;
                        parser.tmp_char = character.unwrap();
                    }
                    if name == String::from("code") && parser.code_start_position.is_some() {
                        let code_found = html.get(parser.code_start_position.unwrap()..begin);
                        parser.code_start_position = None;
                        parser.tmp_codes.push(code_found.unwrap());
                    }
                    if name == String::from("tr") {
                        for code in &parser.tmp_codes {
                            parser.map.insert(parser.tmp_char.to_string(), code.to_string());
                        }
                        parser.tmp_char = "";
                        parser.tmp_codes.clear();
                        parser.character_start_position = None;
                        parser.code_start_position = None;
                    }
                }
                _ => {}
            }

            parser
        })
        .map;

        map


}

I think you're mis-using fold here. You really just want access to the mutable Parser while you iterate, you don't need to replace it each time. (Note how you're trying to replace it with itself each time.)

And you do have access to it! I suggest just rewriting the iteration to use for element in iterator.

-    let mut init = Parser {
+    let mut parser = Parser {
        /* ... */
    };

-    let map = iterator
-        .fold(&mut init, |parser, element| {
+    for element in iterator {
            match element {
                /* ... */
                _ => {}
            }
-
-            parser
-        })
-        .map;
+    }

-    map
+    parser.map

Playground sketch.

3 Likes

For completeness on how fold would work in this context and using @quinedot's playground as a starting point, this example compiles. (Note: I removed map from Parser since it now is kept outside of the parser)

playground

    iterator.fold(HashMap::new(), |mut map, element| {
        ...
                    for code in &parser.tmp_codes {
                        map.insert(parser.tmp_char.to_string(), code.to_string());
                    }
        ...
        map
    })
1 Like

Oh thank you @quinedot ! I was coding something too complexe :wink: