15

This code works:

let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let mut hmap = HashMap::<String, u64>::new();

rdr.records()
    .map(|r| r.unwrap())
    .fold((), |_, item| {
        // TODO: Is there a way not to have to copy item[col] every time?
        let counter = hmap.entry(item[col].to_string()).or_insert(0);
        *counter += 1;
    });

This code fails with the message: "cannot move out of acc because it is borrowed"

let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let hmap = rdr.records()
    .map(|r| r.unwrap())
    .fold(HashMap::<String, u64>::new(), |mut acc, item| {
        // TODO: Is there a way not to have to copy item[col] every time?
        let counter = acc.entry(item[col].to_string()).or_insert(0);
        *counter += 1;
        acc
    });
Christopher Davies
  • 4,461
  • 2
  • 34
  • 33
  • 2
    How are we supposed to know what `rdr.records` is? Please make sure you create an [MCVE](/help/mcve) when asking questions on Stack Overflow. – Shepmaster Aug 07 '15 at 18:25
  • because you're trying to move instead of use a reference, `|mut acc, item|` will move which you probably don't want but rather a `&` ref – Syntactic Fructose Aug 07 '15 at 18:29
  • The type of rdr doesn't seem to be the problem, which is why I left it out. But as for the ref, I have tried that, and it gives me other errors. I'll post the details in a bit. – Christopher Davies Aug 07 '15 at 19:22
  • 1
    Why fold at all? A mutating loop is much more obvious: `let mut hmap = HashMap::::new(); for _ in &[1, 2, 3] { *hmap.entry("foo".into()).or_insert(0) += 1; }`. – Veedrac Aug 08 '15 at 15:29
  • Because I'm doing essentially a reduce. Taking a list and reducing it to a single value, even though that value is a dictionary. I do like your approach, though. Maybe Rob Pike is right, and people should stop using filter/map/reduce and just use for loops properly! – Christopher Davies Aug 09 '15 at 01:47

1 Answers1

25

You cannot return acc from the closure because you have a mutable borrow to it that still exists (counter).

This is a limitation of the Rust compiler (specifically the borrow checker). When non-lexical lifetimes are enabled, your original code will work:

#![feature(nll)]

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        let counter = acc.entry("foo".to_string()).or_insert(0);
        *counter += 1;
        acc
    });

    println!("{:?}", hmap);
}

Before NLL, the compiler is overly conservative about how long a borrow will last. To work around this, you can introduce a new scope to constrain the mutable borrow:

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        {
            let counter = acc.entry("foo".to_string()).or_insert(0);
            *counter += 1;
        }
        acc
    });

    println!("{:?}", hmap);
}

You can also prevent the borrow from lasting beyond the line it's needed in:

use std::collections::HashMap;

fn main() {
    let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
        *acc.entry("foo".to_string()).or_insert(0) += 1;
        acc
    });

    println!("{:?}", hmap);
}

I assumed Rust would know that counter would go out of scope once acc was returned

This is understandable and relates to the non-lexical lifetimes discussion. The "good" news is that Rust is being consistent about how references work when the thing being referenced moves. In this case, you are moving the accumulator into an "output slot". You can see this with plain functions as well:

fn foo(mut s: Vec<u8>) -> Vec<u8> {
    let borrow = &mut s[0];
    s
}

fn main() {}

But really, it's the same as moving a referred-to variable at all:

fn main() {
    let mut s = Vec::<u8>::new();
    let borrow = &mut s[0];
    let s2 = s;
}

Both of these fail before NLL and work afterwards.

aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Thanks! That makes total sense. I assumed rust would know that counter would got out of scope once acc was returned, so there wouldn't be an issue. But I can see how it might not have known that. – Christopher Davies Aug 07 '15 at 20:27