Search code examples
filerust

using std::fs::read_dir as an iterator in Rust


I'm new to Rust(about a month), and this is my first time asking a question in this platform. please excusing me for making a few mistakes. Right now, I'm trying to make a save system that copies the whole directory. It looks like this:

[File hierarchy]

Documents

  • tosave
  • logs
  • write_log (the thing I'm making)

[main.rs]

use std::fs::{copy,create_dir,read_dir};
use std::path::{Path,PathBuf};

fn copy_dir(cp_path: &Path,wr_path: &Path){
    // if cp_path is a directory, repeat self to every components
    if cp_path.is_dir(){
        create_dir(&wr_path);
        for each_file in read_dir(cp_path) {
            //dynamic path to edit
            let mut pathbuf = PathBuf::new();
            pathbuf.push(&wr_path);
            match each_file {
                Ok(i) => {
                    let file_path = i.path().as_path();
                    match file_path.file_name(){
                        Some(name) =>{
                            let filename = Path::new(&name);
                            pathbuf.push(&filename);
                            //recurse
                            copy_dir(&file_path,&pathbuf.as_path());
                        }
                        None => {
                            //create an empty dir if name isn't valid
                            pathbuf.push(&Path::new("N/A"));
                            create_dir(pathbuf.as_path());
                        }
                    }
                }
                Err(e) => {
                    panic!();
                }
            }
        }
    }else{  copy(&cp_path,&wr_path); }
}

fn main() {
    let copy_path = Path::new("tosave");
    let write_path = Path::new("logs/foo");
    copy_dir(&copy_path,&write_path);
}

When I tried to compiled the code, an error occurred.

error[E0308]: mismatched types --> src/main.rs:12:17 | 11 |
match each_file { | --------- this expression has type ReadDir 12 | Ok(i) => { |
^^^^^ expected ReadDir, found Result<_, _> | = note: expected struct ReadDir found enum `Result<_, _>

I was expecting each_file's type to be Option<Result<DirEntry>>. but it appears the for ~ in loop didn't function properly, and it somehow gave each_file a ReadDir. please help me on this.

(documents I referenced)

fn read_dir

struct ReadDir


Solution

  • If we remove the loop body, the compiler can be of help:

    fn copy_dir(cp_path: &Path, wr_path: &Path) {
        // if cp_path is a directory, repeat self to every components
        if cp_path.is_dir() {
            create_dir(&wr_path);
            for each_file in read_dir(cp_path) {
            }
        } else {
            copy(&cp_path, &wr_path);
        }
    }
    
    warning: for loop over a `Result`. This is more readably written as an `if let` statement
     --> src/main.rs:8:26
      |
    8 |         for each_file in read_dir(cp_path) {
      |                          ^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(for_loops_over_fallibles)]` on by default
    help: to check pattern in a loop use `while let`
      |
    8 |         while let Ok(each_file) = read_dir(cp_path) {
      |         ~~~~~~~~~~~~~         ~~~
    help: consider using `if let` to clear intent
      |
    8 |         if let Ok(each_file) = read_dir(cp_path) {
    

    Playground.

    What happens is that read_dir() returns Result<ReadDir, std::io::Error>. You intended to iterate over the ReadDir (which is an iterator over Result<DirEntry, std::io::Error>, but you are actually iterating over the Result. This is not a direct error because Result implements IntoIterator - it yields the Ok value if Ok, or nothing if Err. This is useful for, e.g. ignoring errors in an iterator by flatten()ing it.

    You need to handle the Result, e.g. by unwrap()ing it. But, since copy is obviously an operation that can fail as it touches the file system, you should make it return the errors it encounters. The better signature for this would be:

    fn copy_dir(cp_path: &Path, wr_path: &Path) -> Result<(), io::Error>
    

    That way, you can handle errors mostly by ?ing them.