【问题标题】:Directory traversal in vanilla Rustvanilla Rust 中的目录遍历
【发布时间】:2020-08-23 02:08:57
【问题描述】:

我是 Rust 新手,并试图了解基本的目录遍历。我发现的几乎所有示例都使用了walkdirglob 库,我已经取得了很好的成功。但是,我现在尝试仅使用 std 库来执行此操作。

标准库文档中有一个primitive example,列出了以下函数:

fn visit(path: &Path, cb: &dyn Fn(&PathBuf)) -> io::Result<()> {
    for e in read_dir(path)? {
        let e = e?;
        let path = e.path();
        if path.is_dir() {
            visit(&path, cb)?;
        } else if path.is_file() {
            cb(&path);
        }
    }
    Ok(())
}

我感到困惑的部分是如何在闭包的上下文中访问cb 函数。我很难找到一个例子。

例如,我想做一些基本的事情,比如将生成的路径收集到 Vec 中。显然,这不起作用:

fn main() {
    // create a new path
    let path = Path::new(PATH);
    let mut files = Vec::new();

    visit(path, |e| {
      files.push(e);
    });
}

我收到的错误是:

expected reference `&dyn for<'r> std::ops::Fn(&'r std::path::PathBuf)`
     found closure `[closure@src/main.rs:24:17: 26:6 files:_]

所以我的问题是,如何返回 Fn 并在闭包上下文中处理结果?

【问题讨论】:

  • 如果您注意 full 错误消息(您没有在此处包含),您会看到提示您应该如何解决错误:help: consider borrowing here: '&amp;|e| files.push(e)' .如果你解决了这个问题,你会发现你还有更多的错误需要解决。

标签: rust


【解决方案1】:

您的代码存在多个问题,但您收到错误消息的第一个问题是因为&amp;dyn Fn(&amp;PathBuf) 需要一个函数的引用。您可以按照错误消息中的建议解决该错误:help: consider borrowing here: '&amp;|e| files.push(e)'

这会把你的电话变成:

visit(path, &|e| files.push(e));

但是,此代码仍然不正确,并导致另一个错误:

error[E0596]: cannot borrow `files` as mutable, as it is a captured variable in a `Fn` closure
  --> playground\src\main.rs:48:22
   |
48 |     visit(path, &|e| files.push(e));
   |                      ^^^^^ cannot borrow as mutable

这一次,是因为您在 Fn(不可变闭包)内对 files 进行了变异。要解决此问题,您需要将函数类型更改为 FnMut(有关更多信息,请参阅 Closures As Input Parameters):

fn visit(path: &Path, cb: &dyn FnMut(&PathBuf))

但你还没有完成。现在出现了另一个错误,但与第一个错误一样,它附带了需要更改的建议:

error[E0596]: cannot borrow `*cb` as mutable, as it is behind a `&` reference
  --> playground\src\main.rs:39:13
   |
32 | fn visit(path: &Path, cb: &dyn FnMut(&PathBuf)) -> io::Result<()> {
   |                           -------------------- help: consider changing this to be a mutable reference: `&mut dyn for<'r> std::ops::FnMut(&'r std::path::PathBuf)`
...
39 |             cb(&path);
   |             ^^ `cb` is a `&` reference, so the data it refers to cannot be borrowed as mutable

为了让您的闭包可变地借用它使用的数据,您还必须对闭包本身进行可变引用,并且您需要更新您的 visit() 调用以匹配:

fn visit(path: &Path, cb: &mut dyn FnMut(&PathBuf))
...
visit(path, &mut |e| files.push(e));

差不多了,但最后一个错误需要解决:

error[E0521]: borrowed data escapes outside of closure
  --> playground\src\main.rs:48:26
   |
47 |     let mut files = Vec::new();
   |         --------- `files` declared here, outside of the closure body
48 |     visit(path, &mut |e| files.push(e));
   |                       -  ^^^^^^^^^^^^^ `e` escapes the closure body here
   |                       |
   |                       `e` is a reference that is only valid in the closure body

您已将闭包定义为引用 PathBuf (&amp;PathBuf),但您试图将这些引用推送到闭包外部的 Vec 中,这是行不通的因为一旦闭包超出范围,这些引用将无效。相反,你应该使用一个拥有的值——简单的PathBuf。您还需要更新对闭包的使用以传递 PathBuf 而不是引用:

fn visit(path: &Path, cb: &mut dyn FnMut(PathBuf))
...
cb(path);

终于成功了!这是完整程序现在的样子。请注意,您还应该unwrap() 调用visit(),因为它返回Result。我还添加了一个简单的for 循环来打印文件名。

use std::path::{Path, PathBuf};
use std::fs::*;
use std::io;

fn visit(path: &Path, cb: &mut dyn FnMut(PathBuf)) -> io::Result<()> {
    for e in read_dir(path)? {
        let e = e?;
        let path = e.path();
        if path.is_dir() {
            visit(&path, cb)?;
        } else if path.is_file() {
            cb(path);
        }
    }
    Ok(())
}

fn main() {
    let path = Path::new("./your/path/here");
    let mut files = Vec::new();
    visit(path, &mut |e| files.push(e)).unwrap();
    for file in files {
        println!("{:?}", file);
    }
}

【讨论】:

    猜你喜欢
    • 2019-10-15
    • 2016-01-22
    • 2016-07-24
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多