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 | Show 2 more comments2 Answers
Reset to default 2If 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>
- becausefor
doesn't yet support async iteration - accesses file name with
path()
, which returns aPath
. If needed, a string can be obtained withinto_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)
}
}
while let
as you realized yourself this is just a obfuscatedloop { let =
– cafce25 Commented Mar 4 at 8:54?
as case_sensitive suggests, you are already returning aResult
– cafce25 Commented Mar 4 at 8:57tokio::fs
doesn't mirrorstd::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