Explosion Pills
Explosion Pills

Reputation: 191819

Read one level of directory structure

I am trying to list all of the directories (not recursively) in a directory using Rust. I am mostly following the example code from the read_dir docs, but I am trying to make it simpler since I know what the directory is ahead of time and I don't need to recurse.

I have it down to:

for entry in read_dir(Path::new("known-directory")) {
    let path = entry.path();

    if path.is_dir() {
        print!("{}", entry);
    }
}

This doesn't work since I get complaints of

no method named `path` found for type `std::fs::ReadDir` in the current scope

It seems like for is not actually iterating over the entries of ReadDir.

I have also tried try!(read_dir(Path::new("DefinitelyTyped"))) similar to what is in the docs, but this yields

expected (), found enum `std::result::Result`

Using let entry = try!(entry) does not work either.

Ultimately I would like to push these directory entries to an array, sort it, and JSON-stringify it but of course first I have to be able iterate through the entries properly. How can I do this?

Version: rustc 1.13.0
Running with: cargo 0.13.0

Upvotes: 2

Views: 2215

Answers (1)

Shepmaster
Shepmaster

Reputation: 432149

Let's read the compiler error messages together!

error: no method named `path` found for type `std::fs::ReadDir` in the current scope
 --> src/main.rs:5:26
  |
5 |         let path = entry.path();
  |                          ^^^^

This means that the type of entry is a ReadDir, but how did that happen? We are supposed to be iterating over a ReadDir!

If we look at the documentation for read_dir, we can see that it returns a Result:

pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir>

This means that the process of reading a directory can fail, which is entirely believable — what if the directory doesn't exist? However, the presented code doesn't handle that error. It instead passes the Result to the for loop. for loops work by calling IntoIterator, and Result implements that, yielding the Ok case or nothing at all. Option has a similar implementation.

So, you added try! to the code...

for entry in try!(fs::read_dir("/etc"))
error[E0308]: mismatched types
 --> src/main.rs:4:18
  |
4 |     for entry in try!(fs::read_dir("/etc")) {
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
  |
  = note: expected type `()`
  = note:    found type `std::result::Result<_, _>`
  = note: this error originates in a macro outside of the current crate

As has been discussed previously and is mentioned in the documentation for try!:

Because of the early return, try! can only be used in functions that return Result.

You have to handle the error somehow, and for whatever reason, your current function states that it cannot fail — it doesn't return a Result! Instead, let's just kill the entire program by panicking by adding expect to the call:

for entry in fs::read_dir("/etc").expect("I told you this directory exists")

(Some people use unwrap, but I will always advocate for expect as it has a higher chance of providing useful information for the poor soul that experiences the eventual failure)

error: no method named `path` found for type `std::result::Result<std::fs::DirEntry, std::io::Error>` in the current scope
 --> src/main.rs:5:26
  |
5 |         let path = entry.path();
  |                          ^^^^

Yes, there are even more failure cases possible. Specifically, reading each entry may fail for some reason. That's why the ReadDir iterator says

type Item = Result<DirEntry>

Again, your function still states it cannot fail, so we have to panic again:

let entry = entry.expect("I couldn't read something inside the directory");
error[E0277]: the trait bound `std::fs::DirEntry: std::fmt::Display` is not satisfied
 --> src/main.rs:9:26
  |
9 |             print!("{}", entry);
  |                          ^^^^^ trait `std::fs::DirEntry: std::fmt::Display` not satisfied
  |
  = note: `std::fs::DirEntry` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
  = note: required by `std::fmt::Display::fmt`

As stated well in the error message, change {} to {:?} because DirEntry has no proper way to be formatted for end-users. Programmers can deal with the debugging format.


use std::fs;

fn main() {
    for entry in fs::read_dir("/etc").expect("I told you this directory exists") {
        let entry = entry.expect("I couldn't read something inside the directory");
        let path = entry.path();

        if path.is_dir() {
            print!("{:?}", entry);
        }
    }
}

I'd highly recommend re-reading the error handling chapter of The Rust Programming Language. I'd also advocate for basically memorizing the methods and traits implemented for Result and Option, seeing as how core they are to the Rust experience.

Here's a version that returns an error from main and uses the try operator (?):

use std::{error::Error, fs};

fn main() -> Result<(), Box<dyn Error>> {
    for entry in fs::read_dir("/etc")? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            print!("{:?}", entry);
        }
    }

    Ok(())
}

Upvotes: 14

Related Questions