最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Iterating over tokio directory entries - Stack Overflow

programmeradmin1浏览0评论

I understand the problem with this code, but I don't know what would be the right way to fix it:

use tokio::fs::read_dir;
use hyper::{Body, Response, Result};

async fn list_dir() -> Result<Response<Body>> {
    let mut paths = read_dir("./").await.unwrap();

    while let result = paths.next_entry().await {
        match result {
            Ok(entry) => {
                let dir_name = entry.unwrap().file_name().into_string().unwrap();
                println!("Name: {}", dir_name);
            }
            Err(e) =>
                println!("Something went wrong: {}", e)
        }
    }

    Ok(Response::new(Body::from("Hello World!")))
}

My understanding is that result will always be something that doesn't evaluate to "false", and so the loop will never terminate.

Given the ungodly amount of explicit error handling and explicit concurrency boilerplate, what was I supposed to do? Do I have to add more unwraps and awaits in the loop condition until the value of result is an Option<X> so that it actually terminates some day? Do I have to write this loop entirely differently and do multiple nested matches and then trust that a break somewhere down the line will terminate the loop?

This library was definitely written with some use-case in mind, which to a newcomer is not at all obvious. Can you please help me understand what was it supposed to be like? Is it possible that somehow the amount of boilerplate would've been less if using a different loop macro?


To explain why I think this code is bad. Apparently, it's not obvious!

I believe that good code needs to avoid dealing with technical objects that are there for the sake of placating the compiler, the linter or the language runtime. So, for instance, the code that deals with reading a directory from a filesystem needs to have files and directories.

Things like "results", "options", "futures", "promises" are all "garbage" that pollutes the program by distracting from the main point: reading the directory list. Ideally, these technical entities should be hidden by the control structures of the program. Eg. the fact that something is an option can be hidden by its use in the condition of a while loop that deals with the technical aspects of it being an option.

Similarly, all this unwrapping and various intermediate variables that serve no purpose other than to beat the program into shape digestible by the compiler are the "cruft", the "garbage" that should be minimized in order to write a better program. A good library writer thus, would try to make sure that users don't need a host of auxiliary variables and functions in order to deal with the library's API.

I understand the problem with this code, but I don't know what would be the right way to fix it:

use tokio::fs::read_dir;
use hyper::{Body, Response, Result};

async fn list_dir() -> Result<Response<Body>> {
    let mut paths = read_dir("./").await.unwrap();

    while let result = paths.next_entry().await {
        match result {
            Ok(entry) => {
                let dir_name = entry.unwrap().file_name().into_string().unwrap();
                println!("Name: {}", dir_name);
            }
            Err(e) =>
                println!("Something went wrong: {}", e)
        }
    }

    Ok(Response::new(Body::from("Hello World!")))
}

My understanding is that result will always be something that doesn't evaluate to "false", and so the loop will never terminate.

Given the ungodly amount of explicit error handling and explicit concurrency boilerplate, what was I supposed to do? Do I have to add more unwraps and awaits in the loop condition until the value of result is an Option<X> so that it actually terminates some day? Do I have to write this loop entirely differently and do multiple nested matches and then trust that a break somewhere down the line will terminate the loop?

This library was definitely written with some use-case in mind, which to a newcomer is not at all obvious. Can you please help me understand what was it supposed to be like? Is it possible that somehow the amount of boilerplate would've been less if using a different loop macro?


To explain why I think this code is bad. Apparently, it's not obvious!

I believe that good code needs to avoid dealing with technical objects that are there for the sake of placating the compiler, the linter or the language runtime. So, for instance, the code that deals with reading a directory from a filesystem needs to have files and directories.

Things like "results", "options", "futures", "promises" are all "garbage" that pollutes the program by distracting from the main point: reading the directory list. Ideally, these technical entities should be hidden by the control structures of the program. Eg. the fact that something is an option can be hidden by its use in the condition of a while loop that deals with the technical aspects of it being an option.

Similarly, all this unwrapping and various intermediate variables that serve no purpose other than to beat the program into shape digestible by the compiler are the "cruft", the "garbage" that should be minimized in order to write a better program. A good library writer thus, would try to make sure that users don't need a host of auxiliary variables and functions in order to deal with the library's API.

Share edited Mar 4 at 14:18 wvxvw asked Mar 4 at 8:43 wvxvwwvxvw 9,57810 gold badges39 silver badges75 bronze badges 7
  • Why not just match on the result in while let as you realized yourself this is just a obfuscated loop { let = – cafce25 Commented Mar 4 at 8:54
  • Or use the try opreator ? as case_sensitive suggests, you are already returning a Result – cafce25 Commented Mar 4 at 8:57
  • @cafce25 How would I know? There is no documentation on this subject and all solutions I can come up with look really bad... The one you offer looks bad too. I want to know what the library author wanted me to do. They had some idea in their mind how this was meant to be used. Maybe it was meant to be atrocious, then I'll just suck it up and do it the way they wanted. As an outsider I cannot read their mind and know what they wanted... – wvxvw Commented Mar 4 at 14:05
  • @cafce25 please refrain from personal remarks. It's not for you to decide for whom Rust is. Also, I assure you, that in the case of iteration over directory entries tokio::fs doesn't mirror std::fs, that's why the question. Anyways, I'd prefer concentrating on the question than on general discussion of Rust. I will have to make these decisions on my own, thank you. – wvxvw Commented Mar 4 at 14:20
  • 2 Comment on question second part: @wvxw your view on "results" & others being "garbage" is interesting, but maybe you missed a point about the whole Rust paradigm: things may fail at various points in a code, especially with async, even in a "simple" directory iteration, and the language makes you, as the writer of the algorithm, responsible for explicit handling of all these possible failures. But I also agree that writing Rust code that feels natural and idiomatic is tough. – Joël Commented Mar 5 at 10:43
 |  Show 2 more comments

2 Answers 2

Reset to default 2

If your question is about the intended/idiomatic way to use read_dir(), that'd be something like:

while let Some(entry) = paths.next_entry().await? {
    println!("Name: {}", entry.path().display());
    // use entry.path() to open the file, etc
}

Playground

To avoid boilerplate, this code:

  • uses ? instead of matching on Ok/Err
  • loops using while let Some(x) = <next element> - because for doesn't yet support async iteration
  • accesses file name with path(), which returns a Path. If needed, a string can be obtained with into_string().unwrap() or, if you don't want to panic, let Some(name) = entry.file_name().into_string() else { continue };.

depend on you desired behavior, here are are some example

use tokio::fs::{read_dir, ReadDir};

#[tokio::main]
async fn main() {
    let mut paths = read_dir("./").await.unwrap();

    // explicit handle all possible route with match
    loop {
        match paths.next_entry().await {
            Ok(Some(entry)) => {
                let dir_name = entry.file_name().into_string().unwrap();
                println!("Name: {}", dir_name);
            }
            Ok(None) => {
                break;
            }
            Err(e) => {
                println!("Something went wrong: {}", e)
            }
        }
    }

    // ignore erorr, the loop until end of list or encounter an error
    // since encountering a read_dir error is rare and hard to recover anyways
    while let Ok(Some(entry)) = paths.next_entry().await {
        let dir_name = entry.file_name().into_string().unwrap();
        println!("Name: {}", dir_name);
    }

    // propagate the error upward
    let res = handle(paths).await;

}

async fn handle(mut read_dir: ReadDir) -> std::io::Result<()> {
    // loop until end of list, and in case of encounter an error, will propagate it upward
    while let Some(entry) = read_dir.next_entry().await? {
        let dir_name = entry.file_name().into_string().unwrap();
        println!("Name: {}", dir_name);
    }
    Ok(())
}

Edit

std::fs::read_dir

use std::fs::{ReadDir, read_dir};

fn main() {
    let paths: ReadDir = read_dir("./").unwrap();

    for p in paths {
        let entry = p.unwrap();
        println!("{:?}", entry)
    }   
}

发布评论

评论列表(0)

  1. 暂无评论