如何使用HashMap作为累加器进行折叠?

Chr*_*ies 10 rust

此代码有效:

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;
    });
Run Code Online (Sandbox Code Playgroud)

此代码失败并显示消息:"无法移出,acc因为它是借用的"

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
    });
Run Code Online (Sandbox Code Playgroud)

She*_*ter 15

你不能acc从闭包中返回,因为你有一个可变的借用它仍然存在(counter).

这是Rust编译器(特别是借用检查器)的限制.当启用非词法生命周期时,您的原始代码将起作用:

#![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);
}
Run Code Online (Sandbox Code Playgroud)

在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);
}
Run Code Online (Sandbox Code Playgroud)

您还可以阻止借款持续超出其所需的界限:

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);
}
Run Code Online (Sandbox Code Playgroud)

我以为Rust会知道counter一旦acc返回就会超出范围

这是可以理解的,涉及非词汇生命周期的讨论."好"的消息是Rust在被引用的东西移动时,引用如何工作是一致的.在这种情况下,您将累加器移动到"输出槽".你也可以通过普通函数看到这个:

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

fn main() {}
Run Code Online (Sandbox Code Playgroud)

但实际上,它与移动引用变量完全相同:

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

这两个都在NLL之前失败并且之后工作.