How can I get an Axum Handler function to return a Vec? - rust

I'm learning how to use Axum with SQLx starting with this example. The basic example works, but I have problem trying to move forward. I am working with a simple database table as shown below:
todo | description
--------+--------------
todo_1 | doing todo 1
todo_2 | doing todo 2
todo_3 | doing todo 3
I am trying to simply get back "SELECT * FROM todos", but I am getting an error. I think I am getting the return of the Result type wrong but I am not sure what to do next. The entirety of main.rs is shown below.
//! Example of application using <https://github.com/launchbadge/sqlx>
//!
//! Run with
//!
//! ```not_rust
//! cd examples && cargo run -p example-sqlx-postgres
//! ```
//!
//! Test with curl:
//!
//! ```not_rust
//! curl 127.0.0.1:3000
//! curl -X POST 127.0.0.1:3000
//! ```
use axum::{
async_trait,
extract::{Extension, FromRequest, RequestParts},
http::StatusCode,
routing::get,
Router,
};
use sqlx::postgres::{PgPool, PgPoolOptions, PgRow};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use std::{net::SocketAddr, time::Duration};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "example_tokio_postgres=debug".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
let db_connection_str = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:postgres#localhost".to_string());
// setup connection pool
let pool = PgPoolOptions::new()
.max_connections(5)
.connect_timeout(Duration::from_secs(3))
.connect(&db_connection_str)
.await
.expect("can connect to database");
// build our application with some routes
let app = Router::new()
.route(
"/",
get(using_connection_pool_extractor).post(using_connection_extractor),
)
.layer(Extension(pool));
// run it with hyper
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// we can extract the connection pool with `Extension`
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<Vec<String>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map_err(internal_error)
}
// we can also write a custom extractor that grabs a connection from the pool
// which setup is appropriate depends on your application
struct DatabaseConnection(sqlx::pool::PoolConnection<sqlx::Postgres>);
#[async_trait]
impl<B> FromRequest<B> for DatabaseConnection
where
B: Send,
{
type Rejection = (StatusCode, String);
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let Extension(pool) = Extension::<PgPool>::from_request(req)
.await
.map_err(internal_error)?;
let conn = pool.acquire().await.map_err(internal_error)?;
Ok(Self(conn))
}
}
async fn using_connection_extractor(
DatabaseConnection(conn): DatabaseConnection,
) -> Result<String, (StatusCode, String)> {
let mut conn = conn;
sqlx::query_scalar("select 'hello world from pg'")
.fetch_one(&mut conn)
.await
.map_err(internal_error)
}
/// Utility function for mapping any error into a `500 Internal Server Error`
/// response.
fn internal_error<E>(err: E) -> (StatusCode, String)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
Compared to the example, I changed this function so that it returns a Vec<String> instead of a plain String, but I get a compiler error:
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<Vec<String>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map_err(internal_error)
}
error[E0277]: the trait bound `fn(Extension<Pool<sqlx::Postgres>>) -> impl Future<Output = Result<Vec<String>, (StatusCode, String)>> {using_connection_pool_extractor}: Handler<_, _>` is not satisfied
--> src/main.rs:52:17
|
52 | get(using_connection_pool_extractor).post(using_connection_extractor),
| --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(Extension<Pool<sqlx::Postgres>>) -> impl Future<Output = Result<Vec<String>, (StatusCode, String)>> {using_connection_pool_extractor}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `axum::handler::Layered<S, T>`
note: required by a bound in `axum::routing::get`
I am not sure what this error is suggesting or if it is even related to the actual problem.

Try using axum::Json:
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<axum::Json<Vec<String>>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map(|todos| axum::Json(todos))
.map_err(internal_error)
}
The reason why is that there's no implementation of the IntoResponse trait for Vec<T>. Here's a longer answer by Axum's author: https://users.rust-lang.org/t/axum-error-handling-trait-question/65530

Related

How to use a struct method as an handler in Axum? [duplicate]

This question already has answers here:
How to call struct method from axum server route?
(1 answer)
Is there a way to create a function pointer to a method in Rust?
(1 answer)
Closed 2 months ago.
This works where the handler is a stand alone function
use axum::{
routing::{get, post},
http::StatusCode,
response::IntoResponse,
Json, Router,
};
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> () {
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/", axum::routing::get(root));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// basic handler that responds with a static string
async fn root() -> impl IntoResponse {
let person = Person {
age: 20,
name: "John".to_string()
};
(StatusCode::OK, Json(person))
}
but in my project, I need to have the logic of the handler as part of a struct impl. I tried doing that but it fails. Code and error is below
use axum::{
routing::{get, post},
http::StatusCode,
response::IntoResponse,
Json, Router,
};
use std::net::SocketAddr;
mod http {
pub struct ServerLogic {
}
impl ServerLogic {
pub fn new() -> Self {
Self {}
}
pub fn hello(&self) -> impl axum::response::IntoResponse {
(axum::http::StatusCode::OK, axum::Json("Hello, world!"))
}
}
}
#[tokio::main]
async fn main() -> () {
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/", axum::routing::get(crate::http::ServerLogic::new().hello));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Error
error[E0615]: attempted to take value of method `hello` on type `ServerLogic`
--> src/main.rs:104:72
|
104 | .route("/", axum::routing::get(crate::http::ServerLogic::new().hello));
| ^^^^^ method, not a field
|
help: use parentheses to call the method
|
104 | .route("/", axum::routing::get(crate::http::ServerLogic::new().hello()));
| ++
How do I get to use the method of a struct as an handler?

Share state between actix-web server and async closure

I want to periodically fetch data (using asynchronous reqwest), which is then served at an http endpoint using actix-web as a server.
(I have a data source that has a fixed format, that I want to have read by a service that require a different format, so I need to transform the data.)
I've tried to combine actix concepts with the thread sharing state example from the Rust book, but I don't understand the error or how to solve it.
This is the code minified as much as I was able:
use actix_web::{get, http, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Mutex<AppState>>) -> impl Responder {
let state = data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.to_owned())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let status_string = get_state().await.unwrap();
let app_data = Arc::new(Mutex::new(web::Data::new(AppState {
status: status_string,
})));
let app_data1 = Arc::clone(&app_data);
actix_web::rt::spawn(async move {
loop {
println!("I get executed every 2-ish seconds!");
sleep(Duration::from_millis(2000)).await;
let res = get_state().await;
let mut app_data = app_data1.lock().unwrap();
// Edit 2: this line is not accepted by the compiler
// Edit 2: *app_data.status = res.unwrap();
// Edit 2: but this line is accepted
*app_data = web::Data::new(AppState { status: res });
}
});
let app_data2 = Arc::clone(&app_data);
// Edit 2: but I get an error here now
HttpServer::new(move || App::new().app_data(app_data2).service(index))
.bind(("127.0.0.1", 9090))?
.run()
.await
}
async fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new().get("http://ipecho.net/plain".to_string());
let status = client.send().await?.text().await?;
println!("got status: {status}");
Ok(status)
}
But I get the following error:
error[E0308]: mismatched types
--> src/main.rs:33:32
|
33 | *app_data.status = res.unwrap();
| ---------------- ^^^^^^^^^^^^ expected `str`, found struct `String`
| |
| expected due to the type of this binding
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:33:13
|
33 | *app_data.status = res.unwrap();
| ^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `str`
= note: the left-hand-side of an assignment must have a statically known size
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
Why do I suddenly get a str? Is there an easy fix or is my approach to solving this wrong?
Edit: Maybe removing the * is the right way to go, as Peter Hall suggests, but that gives me the following error instead:
error[E0594]: cannot assign to data in an `Arc`
--> src/main.rs:33:13
|
33 | app_data.status = res.unwrap();
| ^^^^^^^^^^^^^^^ cannot assign
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<AppState>`
error[E0507]: cannot move out of `app_data2`, a captured variable in an `Fn` closure
--> src/main.rs:38:49
|
37 | let app_data2 = Arc::clone(&app_data);
| --------- captured outer variable
38 | HttpServer::new(move || App::new().app_data(app_data2).service(index))
| ------- ^^^^^^^^^ move occurs because `app_data2` has type `Arc<std::sync::Mutex<Data<AppState>>>`, which does not implement the `Copy` trait
| |
| captured by this `Fn` closure
Some errors have detailed explanations: E0507, E0594.
For more information about an error, try `rustc --explain E0507`.
Edit 2: I now get the following error (code changes commented with 'Edit 2' above):
error[E0507]: cannot move out of `app_data2`, a captured variable in an `Fn` closure
--> src/main.rs:46:49
|
45 | let app_data2 = app_data.clone();
| --------- captured outer variable
46 | HttpServer::new(move || App::new().app_data(app_data2).service(index))
| ------- ^^^^^^^^^ move occurs because `app_data2` has type `Arc<Mutex<Data<AppState>>>`, which does not implement the `Copy` trait
| |
| captured by this `Fn` closure
For more information about this error, try `rustc --explain E0507`.
My Cargo.toml dependencies:
[dependencies]
actix-web = "4.2.1"
reqwest = "0.11.12"
tokio = "1.21.2"
async solution
I had my types mixed up a bit, having the app state as Arc<Mutex<T>> seemed to be the way to go, maybe it would be better with Arc<RwLock<T>>.
use actix_web::{get, http, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Arc<Mutex<AppState>>>) -> impl Responder {
let state = data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.to_owned())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let status_string = get_state().await.unwrap();
let app_data = Arc::new(Mutex::new(AppState {
status: status_string,
}));
let app_data1 = app_data.clone();
actix_web::rt::spawn(async move {
loop {
println!("I get executed every 2-ish seconds!");
sleep(Duration::from_millis(2000)).await;
let res = get_state().await.unwrap();
let mut app_data = app_data1.lock().unwrap();
*app_data = AppState { status: res };
}
});
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_data.clone()))
.service(index)
})
.bind(("127.0.0.1", 9090))?
.run()
.await
}
async fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new().get("http://ipecho.net/plain".to_string());
let status = client.send().await?.text().await?;
println!("got status: {status}");
Ok(status)
}
async/sync solution
Instead of doing the async get with reqwest I have a solution with the synchronous crate minreq (that I found after a lot of searching). I also chose to not use the #[actix_web::main] macro, and instead start the runtime explicitly at the end of my main function.
use actix_web::{get, http, rt, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Arc<Mutex<AppState>>>) -> impl Responder {
let state = &data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.clone())
}
fn main() -> std::io::Result<()> {
let status_string = get_state().unwrap();
let app_data = Arc::new(Mutex::new(AppState {
status: status_string,
}));
let app_data1 = Arc::clone(&app_data);
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(2000));
let res = get_state().unwrap();
let mut app_data = app_data1.lock().unwrap();
*app_data = AppState { status: res };
});
rt::System::new().block_on(
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_data.clone()))
.service(index)
})
.bind(("127.0.0.1", 9090))?
.run(),
)
}
fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let resp = minreq::get("http://ipecho.net/plain").send().unwrap();
let state = resp.as_str().unwrap();
Ok(state.to_string())
}

Making parallel requests to Redis with Fred

I'm a complete Rust newbie and just tried to convert a simple microservice to Rust. It needs to do many parallel Redis requests for HTTP requests it gets and I'm a bit puzzled with the language syntax. I'm trying to do multiple Redis queries in parallel in an actix-web handler. I have the following type for the Redis GET function in Fred:
fn get<R, K>(&self, key: K) -> AsyncResult<R>
where
R: FromRedis + Unpin + Send,
K: Into<RedisKey>,
docs here: https://docs.rs/fred/5.2.0/fred/interfaces/trait.KeysInterface.html#method.get
In my own code I would then have a for-loop like this:
let resp_futures = vec!{};
for key in keys.iter() {
resp_futures.push(state.redis.get(key));
}
let resps = join_all(resp_futures).await;
Each Redis query should basically return an Option. However, this doesn't work due to some issues with type inference. Any ideas what's the correct way to send parallel Redis requests with the Fred Redis library? The complete server with some unnecessary stuff removed is the following:
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use actix_web::http::header::ContentType;
use serde::Deserialize;
use std::env;
use std::sync::Arc;
use fred::prelude::*;
use fred::pool::RedisPool;
use futures::future::join_all;
//
// Server configuration
//
fn get_redis_url() -> String {
match env::var("REDIS_URL") {
Err(_) => panic!("REDIS_URL environment variable not set."),
Ok(s) => s
}
}
struct AppState {
redis: Arc<RedisPool>
}
//
// Request parsing
//
fn get_redis_keys_from_accounts(accounts: Option<&String>) -> Vec<String> {
match accounts {
None => vec!{},
Some(s) => s.split(",").map(|s| "sid:".to_owned() + s).collect()
}
}
fn get_redis_keys_from_app_accounts(accounts: Option<&String>) -> Vec<String> {
match accounts {
None => vec!{},
Some(s) => s.split(",").map(|s| "aid:".to_owned() + s).collect()
}
}
fn get_redis_keys(req: web::Form<RouteRequest>) -> Vec<String> {
let ids = get_redis_keys_from_accounts(req.ids.as_ref());
let accounts = get_redis_keys_from_app_accounts(req.accounts.as_ref());
let aliases = get_redis_keys_from_app_accounts(req.aliases.as_ref());
ids.into_iter().chain(accounts.into_iter()).chain(aliases.into_iter()).collect()
}
//
// Request handling
//
#[derive(Debug, Deserialize)]
struct RouteRequest {
ids: Option<String>,
accounts: Option<String>,
aliases: Option<String>
}
#[post("/v1/route")]
async fn route(state: web::Data<AppState>, req: web::Form<RouteRequest>) -> impl Responder {
let keys = get_redis_keys(req);
// TODO: Fix this!
let resp_futures = vec!{};
for key in keys.iter() {
resp_futures.push(state.redis.get(key));
}
let resps = join_all(resp_futures).await;
HttpResponse::Ok().content_type(ContentType::json()).body(r#"{"status": "ok"}"#)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Connecting to Redis backend");
let url = get_redis_url();
let config = match RedisConfig::from_url(&url) {
Ok(x) => x,
Err(_) => panic!("Invalid redis URL")
};
let policy = ReconnectPolicy::default();
let redis = Arc::new(match RedisPool::new(config, 5) {
Ok(x) => x,
Err(_) => panic!("Unable to create Redis connection pool")
});
let _ = redis.connect(Some(policy));
println!("Starting HTTP server");
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(AppState {redis: redis.clone()}))
.service(route)
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
Output of cargo check is :
error[E0698]: type inside `async fn` body must be known in this context
--> src/main.rs:76:39
|
76 | resp_futures.push(state.redis.get(key));
| ^^^ cannot infer type for type parameter `R` declared on the associated function `get`
|
note: the type is part of the `async fn` body because of this `await`
--> src/main.rs:78:39
|
78 | let resps = join_all(resp_futures).await;
| ^^^^^^
At line 70 you have to give a hint about the type which should be used. For a String the following line should be used:
resp_futures.push(state.redis.get::<String, _>(key));

Pass database reference through a FromRequest

I'm using the Rocket web framework in Rust and I have a database pool (DbConn).
#[database("redirect-api")]
pub struct DbConn(diesel::PgConnection);
I have been able to pass DbConn to each route but I am having trouble when trying to pass it to a function that is called by from_request. In get_redirects I use the ApiKey struct, and how I understand it, FromRequest is a guard that checks the input. In from_request, I call the is_valid function but can't pass it the DbConn, like I could if I was calling is_valid from a route, like directly from get_redirects.
I want to be able to use DbConn in the is_valid function, but I get this error if I try to use &DbConn
the trait `diesel::Connection` is not implemented for `fn(PooledConnection<<diesel::PgConnection as Poolable>::Manager>) -> DbConn {DbConn}`
|
43 | .load::<Token>(&DbConn)
| ^^^^^^^ the trait `diesel::Connection` is not implemented for `fn(PooledConnection<<diesel::PgConnection as Poolable>::Manager>) -> DbConn {DbConn}`
|
= note: required because of the requirements on the impl of `LoadQuery<fn(PooledConnection<<diesel::PgConnection as Poolable>::Manager>) -> DbConn {DbConn}, models::Token>` for `diesel::query_builder::SelectStatement<schema::tokens::table, query_builder::select_clause::DefaultSelectClause, query_builder::distinct_clause::NoDistinctClause, query_builder::where_clause::WhereClause<diesel::expression::operators::Eq<schema::tokens::columns::token, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>, query_builder::order_clause::NoOrderClause, query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<BigInt, i64>>>`
#![feature(decl_macro)]
use rocket::{catchers, routes};
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate rocket_contrib;
use diesel::prelude::*;
// --snip--
#[database("redirect-api")]
pub struct DbConn(diesel::PgConnection);
// --snip--
fn is_valid(key: &str) {
// ... use key, hash is, etc
// check if hash is in database
// this function needs the database pool or DbConn
// but it can't be passed through FromRequest
}
impl<'a, 'r> FromRequest<'a, 'r> for ApiKey {
type Error = ApiKeyError;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let keys: Vec<_> = request.headers().get("x-api-key").collect();
match keys.len() {
0 => Outcome::Failure((Status::BadRequest, ApiKeyError::Missing)),
1 if is_valid(keys[0]) => Outcome::Success(ApiKey(keys[0].to_string())),
1 => Outcome::Failure((Status::BadRequest, ApiKeyError::Invalid)),
_ => Outcome::Failure((Status::BadRequest, ApiKeyError::BadCount)),
}
}
}
// --snip--
#[get("/redirects")]
pub fn get_redirects(_key: ApiKey, conn: DbConn) -> String {
// ... do database things with conn
format!("something {}", some_value_from_db)
}
// --snip--
fn main() {
rocket::ignite()
.register(catchers![not_found])
.attach(DbConn::fairing())
.mount("/", routes![root, get_redirect])
.launch();
}

How to write an asynchronous recursive walkdir function with an asynchronous callback

I'm trying to write an async function that will traverse the filesystem tree, recursively, and calls an asynchronous callback for each file found.
This is for a learning effort, I have no real use case.
Here is what I have so far:
use async_std::{
fs::{self, *},
path::*,
prelude::*,
}; // 1.5.0, features = ["unstable"]
use futures::{
executor::block_on,
future::{BoxFuture, FutureExt},
}; // 0.3.4
use std::{marker::Sync, pin::Pin};
fn main() {
fn walkdir<F>(path: String, cb: &'static F) -> BoxFuture<'static, ()>
where
F: Fn(&DirEntry) -> BoxFuture<()> + Sync + Send,
{
async move {
let mut entries = fs::read_dir(&path).await.unwrap();
while let Some(path) = entries.next().await {
let entry = path.unwrap();
let path = entry.path().to_str().unwrap().to_string();
if entry.path().is_file().await {
cb(&entry).await
} else {
walkdir(path, cb).await
}
}
}
.boxed()
}
let foo = async {
walkdir(".".to_string(), &|entry: &DirEntry| async {
async_std::println!(">> {}\n", &entry.path().to_str().unwrap()).await
})
.await
};
block_on(foo);
}
I get this far by some sort of trial and error, but now I'm stuck on async closure callback with this error
warning: unused import: `path::*`
--> src/main.rs:3:5
|
3 | path::*,
| ^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused import: `pin::Pin`
--> src/main.rs:10:25
|
10 | use std::{marker::Sync, pin::Pin};
| ^^^^^^^^
error[E0308]: mismatched types
--> src/main.rs:33:54
|
33 | walkdir(".".to_string(), &|entry: &DirEntry| async {
| ______________________________________________________^
34 | | async_std::println!(">> {}\n", &entry.path().to_str().unwrap()).await
35 | | })
| |_________^ expected struct `std::pin::Pin`, found opaque type
|
= note: expected struct `std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = ()> + std::marker::Send>>`
found opaque type `impl core::future::future::Future`
use async_std::{
fs::{self, *},
path::*,
prelude::*,
}; // 1.5.0
use futures::{future::{Future, FutureExt, LocalBoxFuture}, executor}; // 0.3.4
fn main() {
async fn walkdir<R>(path: impl AsRef<Path>, mut cb: impl FnMut(DirEntry) -> R)
where
R: Future<Output = ()>,
{
fn walkdir_inner<'a, R>(path: &'a Path, cb: &'a mut dyn FnMut(DirEntry) -> R) -> LocalBoxFuture<'a, ()>
where
R: Future<Output = ()>,
{
async move {
let mut entries = fs::read_dir(path).await.unwrap();
while let Some(path) = entries.next().await {
let entry = path.unwrap();
let path = entry.path();
if path.is_file().await {
cb(entry).await
} else {
walkdir_inner(&path, cb).await
}
}
}.boxed_local()
}
walkdir_inner(path.as_ref(), &mut cb).await
}
executor::block_on({
walkdir(".", |entry| async move {
async_std::println!(">> {}", entry.path().display()).await
})
});
}
Notable changes:
Take in AsRef<Path> instead of a String and a generic closure instead of a trait object reference
Change the closure type to be FnMut as it's more permissive
The closure returns any type that is a future.
There's an inner implementation function that hides the ugly API required for recursive async functions.
The callback takes the DirEntry by value instead of by reference.
See also:
How to asynchronously explore a directory and its sub-directories?
How to using async fn callback in rust

Resources