首页 > 解决方案 > 为什么使用`flat_map`时需要收集到向量中?

问题描述

我正在从事Project Euler 96以自学 Rust。我编写了这段代码来读取文件并将其转换为整数向量(Playground)。

let file = File::open(&args[1]).expect("Sudoku file not found");
let reader = BufReader::new(file);

let x = reader
    .lines()
    .map(|x| x.unwrap())
    .filter(|x| !x.starts_with("Grid"))
    .flat_map(|s| s.chars().collect::<Vec<_>>())  // <-- collect here!
    .map(|x| x.to_digit(10).unwrap())
    .collect::<Vec<_>>();

这一切都很好,但我很困惑为什么我必须收集到我的向量中flat_map(我假设创建将立即销毁的不需要的向量是低效的)。如果我不收集,它不会编译:

error[E0515]: cannot return value referencing function parameter `s`
  --> src/main.rs:13:23
   |
13 |         .flat_map(|s| s.chars())
   |                       -^^^^^^^^
   |                       |
   |                       returns a value referencing data owned by the current function
   |                       `s` is borrowed here

文档中的示例显示了几乎相同的代码,但不需要收集:

let words = ["alpha", "beta", "gamma"];

// chars() returns an iterator
let merged: String = words.iter()
                          .flat_map(|s| s.chars())
                          .collect();
assert_eq!(merged, "alphabetagamma");

那么为什么我的代码不同呢?

标签: rustlifetimeborrow-checker

解决方案


迭代器reader.lines().map(|x| x.unwrap())迭代String项目,即按值。因此,在 中.flat_map(|s| ...),变量s具有类型String(即拥有,未借用)。换句话说:字符串现在是一个局部变量并且存在于函数中。这是一个简单的规则,您不能返回对局部变量的引用(请参阅此 Q&A)。但这正是s.chars()它的作用,即使它有点隐藏。

看看:_str::chars

pub fn chars(&self) -> Chars<'_>

可以看到字符串是借来的。返回的Chars对象包含对原始字符串的引用。这就是为什么我们不能s.chars()从关闭中返回。

那么为什么我的代码不同呢?

在文档的示例中,迭代器words.iter()实际上迭代了 type 的项目&&'static str。调用s.chars()也将返回一个Chars借用字符串的对象,但该字符串的生命周期是(永远存在),因此从该闭包'static返回没有问题。Chars

解决方案?

如果标准库有一个OwnedChars迭代器,它会消耗 a String,并且在迭代器被删除后像Chars和删除字符串一样工作,那就太好了。在这种情况下,可以调用s.owned_chars(),因为返回的对象没有引用 local s,而是拥有它。但是:标准库中不存在这样的自有迭代器!

我假设创建将立即销毁的不需要的向量是低效的

是的,在某种程度上确实如此。但是您可能已经错过了reader.lines()迭代器也创建了类型的临时对象String。那些也或多或少地立即被摧毁!所以即使没有collectin ,flat_map你也有一堆不必要的分配。请注意,有时这没关系。在这种情况下,我猜与您必须实现的实际算法相比,输入解析非常快。所以……只是collect?在这种情况下可能没问题。

如果您想进行高性能输入解析,我认为您将无法避免标准循环,特别是为了避免不必要的String分配。(游乐场

let mut line = String::new();
let mut input = Vec::new();
loop {
    line.clear(); // clear contents, but keep memory buffer

    // TODO: handle IO error properly
    let bytes_read = reader.read_line(&mut line).expect("IO error"); 
    if bytes_read == 0 {
        break;
    }

    if line.starts_with("Grid") {
        continue;
    }

    // TODO: handle invalid input error
    input.extend(line.trim().chars().map(|c| c.to_digit(10).unwrap()));
}

推荐阅读