1

I'm new to Rust and trying to understand basic directory traversal. Nearly all the examples I have found utilize the walkdir or glob library, which I've had good success with. However, I'm trying to do this now with just the std lib.

There is a primitive example in the standard lib docs listing the following function:

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(()) } 

The part I'm confused about is how to access the cb function in the context of a closure. I'm having a hard time finding an example.

For instance, I want to do something basic like collect the resulting paths into a Vec. Obviously, this does not work:

fn main() { // create a new path let path = Path::new(PATH); let mut files = Vec::new(); visit(path, |e| { files.push(e); }); } 

The error I'm receiving is:

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

So my question is, how can I return a Fn and process the result in a closure context?

1
  • If you pay attention to the full error message (which you did not include here) you will see a hint toward what you should do to resolve the error: help: consider borrowing here: '&|e| files.push(e)'. If you fix that, you will see that you have further errors that need resolving. Commented Aug 23, 2020 at 4:33

1 Answer 1

5

There are multiple issues with your code, but the first one that you are getting the error message for is because &dyn Fn(&PathBuf) expects a reference to a function. You can resolve that error by following the suggestion from the error message: help: consider borrowing here: '&|e| files.push(e)'

That turns your call into:

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

However, this code is still incorrect and results in yet another error:

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 

This time, it's because you're mutating files inside a Fn (immutable closure). To fix that, you need to change your function type to FnMut (see Closures As Input Parameters for more information):

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

But you're still not done. There is now another error, but like the first, it comes with a suggestion for what needs to be changed:

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 

In order for your closure to mutably borrow the data it uses, you also have to take a mutable reference to the closure itself, and you'll need to update your visit() call to match:

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

Almost there, but there is one final error to resolve:

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 

You've defined your closure to take a reference to a PathBuf (&PathBuf), but you're trying to push those references into a Vec that is outside of the closure, which won't work because those references will be invalid once the closure goes out of scope. Instead, you should use an owned value -- simply PathBuf. You'll also need to update your usage of the closure to pass the PathBuf instead of a reference:

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

It finally works! Here is what the full program looks like now. Note that you should also unwrap() your call to visit() since it returns a Result. I've also added a simple for loop to print out the file names.

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); } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.