Function to list files in directories and in subdirectories

I tried to make function to list files in directories and sub-directories recursively but, how to give lifetimes so that, it can compile.

fn _list_files<'a, 'b>(vec: &'a mut Vec<&'b Path>, path: &'a Path) {
    if metadata(&path).unwrap().is_dir() {
        let paths = fs::read_dir(&path).unwrap();
        for path_result in paths {
            let full_path = path_result.unwrap().path();
            if metadata(&full_path).unwrap().is_dir() {
                _list_files(vec, &*full_path);
            } else {
                vec.push(&*full_path);
            }
        }
    }
}

fn list_files<'a>(path: &'a Path) -> Vec<&'a Path> {
    let mut vec = Vec::new();
    _list_files(&mut vec,&path);
    vec
}

Change the return type from Vec<&'a Path> to Vec<PathBuf>. References should generally be avoided in return types unless you know you need them. Particularly when learning Rust. In this case, full_path is of type PathBuf already and you are trying to store a reference to it in the Vec but the memory it is referencing will be dropped at the end of each iteration of the loop in _list_files. With that change the code looks like this

fn _list_files(vec: &mut Vec<PathBuf>, path: &Path) {
    if metadata(&path).unwrap().is_dir() {
        let paths = fs::read_dir(&path).unwrap();
        for path_result in paths {
            let full_path = path_result.unwrap().path();
            if metadata(&full_path).unwrap().is_dir() {
                _list_files(vec, &full_path);
            } else {
                vec.push(full_path);
            }
        }
    }
}

fn list_files(path: &Path) -> Vec<PathBuf> {
    let mut vec = Vec::new();
    _list_files(&mut vec,&path);
    vec
}

Playground

1 Like

Just for good measure. Here's a version that's a bit more idiomatic that gets rid of the unwrap() calls, and instead lets the caller handle errors how they want. Otherwise on an error, unwrap() will cause a panic and end the program.

fn _list_files(vec: &mut Vec<PathBuf>, path: &Path) -> io::Result<()> {
    if metadata(&path)?.is_dir() {
        let paths = fs::read_dir(&path)?;
        for path_result in paths {
            let full_path = path_result?.path();
            if metadata(&full_path)?.is_dir() {
                _list_files(vec, &full_path)?
            } else {
                vec.push(full_path);
            }
        }
    }
    Ok(())
}

fn list_files(path: &Path) -> io::Result<Vec<PathBuf>> {
    let mut vec = Vec::new();
    _list_files(&mut vec,&path)?;
    Ok(vec)
}

Playground

1 Like

If you're writing this to learn, that's great, but if you just want something that works, the walkdir crate is very popular.

2 Likes

Thanks for the help.

Thanks for the suggestion

Thanks, I made function more generalized. So, here's my final function

fn _list_files(vec: &mut Vec<PathBuf>, path: PathBuf) -> io::Result<()>  {
    if path.is_dir() {
        let paths = fs::read_dir(&path)?;
        for path_result in paths {
            let full_path = path_result?.path();
            _list_files(vec, full_path);
        }
    } else {
        vec.push(path);
    }
    Ok(())
}

fn list_files<T: Into<PathBuf>>(path: T) -> io::Result<Vec<PathBuf>> {
    let mut vec = Vec::new();
    let path = path.into();
    _list_files(&mut vec, path);
    Ok(vec)
}

Can it be improved anymore?

What you have looks good. In the standard library, it's common to use AsRef<Path> as the argument type for anything that takes a path. That type is similarly generic over a bunch of types, but doesn't need to take ownership. In your case though, since you're returning Vec<PathBuf>, I think what you have is the best choice.

Thanks🙂

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.