在 Hashmap 或类似 Rust 中可重用的异步闭包/函数,Rust

Reuseable async-closures/functions in a Hashmap or alike, Rust

提问人:Mads Ahlquist Jensen 提问时间:3/21/2023 最后编辑:Mads Ahlquist Jensen 更新时间:3/21/2023 访问量:231

问:

我想创建一个结构,它有四个方法:new(), handle_interaction(), add_shortcut(), async start_server()

该结构应包含一个 Web 服务器,该服务器使用用户添加的可用快捷方式(回调闭包)列表启动。async start_server()

当服务器启动时,我希望有一个端点 (),然后它将接收 JSON 数据。当收到 json 数据时,服务器将确定传入的交互,并同时启动交互闭包。/

我尝试了很多不同的解决方案,我必须说,其中很多都包含了我还不熟悉的 rust 类型和概念。

但是,我现在登陆的代码仍然失败,是这样的: (我用注释突出显示了错误,只是为了让您知道失败的原因)

use axum::{
    routing::{get, post},
    http::StatusCode,
    Json, Router,
};
use std::{net::SocketAddr, future::Future, pin::Pin, collections::HashMap};
use tracing::{debug, info, error};
use futures::{FutureExt, future::BoxFuture};


pub struct Interaction {
    r#type: String,
}

#[derive(Default)]
pub struct App {
    router: Router,
    shortcuts: HashMap<String, Box<dyn Fn(Interaction) -> BoxFuture<'static, ()>>>
}

impl App {
    pub fn new() -> Self {
        Self::default()
    }

    async fn handle_interaction(
        &self,
        Json(interaction): Json<Interaction> 
    ) -> StatusCode {
        let t = interaction.r#type.clone();
        debug!("Got shortcut");
        debug!("{t}");

        let closure = self.shortcuts.get(&t).unwrap();
        tokio::task::spawn(closure(interaction));

        StatusCode::OK
    }

    pub fn add_shortcut<C, Fut>(
        mut self, 
        callback_id: &str,
        fun: C,
    ) -> Self 
    where
        Fut: Future<Output = ()> + Send + 'static,
        C: Fn(Interaction) -> Pin<Box<Fut>>,
    {
        self.shortcuts.insert(callback_id.to_string(),  Box::new(move |interaction| Box::pin(fun(interaction))));
        self
    }

    async fn start(self, addr: SocketAddr) {
        let interaction_handler = move |interaction| async move {
            self.handle_interaction(interaction).await
        };

        // ERROR: `interaction_handler` doesn't match axums route-types... Don't know why this is the case either.
        let router = Router::new()
            .route("/", post(interaction_handler));

        debug!("listening on {}", addr);

        axum::Server::bind(&addr)
            .serve(self.router.into_make_service())
            .await
            .unwrap();
    }
}

async fn shortcut1(i: Interaction) {
    println!("Hello, World!");
}

async fn shortcut2(i: Interaction) {
    println!("Hello, Other World!")
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    // ERROR: `shortcut1` and `shortcut2` doesn't match input type, 
    // and i don't know how to do this properly.
    let app = App::new()
        .add_shortcut("shortcut1", shortcut1)
        .add_shortcut("shortcut2", shortcut2);

    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    app.start(addr).await;
}

其中很多是跟踪和错误、chatgpt 和随机文档的结果——我不熟悉 box、pin 等。

我也读过 Mutex 或 Arc 可能有助于解决这个问题,但我也无法让它起作用。

我希望这里有人能帮助我指出正确的方向!

异步 闭包 Rust-Tokio Rust-Axum

评论

0赞 Chayim Friedman 3/21/2023
您不运行服务器。
0赞 Mads Ahlquist Jensen 3/21/2023
我会编辑这个 - 谢谢。由于上述错误,服务器无论如何都不会启动。
1赞 cafce25 3/21/2023
如果您遇到错误,请发布来自编译器的完整错误消息,而不仅仅是您的 IDE 可能向您显示的一些小摘录。
0赞 Mads Ahlquist Jensen 3/24/2023
我只是将其标记为已解决 - 但我意识到这是多么愚蠢。我会在以后的帖子中记住这一点!

答:

0赞 Chayim Friedman 3/21/2023 #1

您的代码存在三个主要问题:

  1. Interaction不实现反序列化,因此不能与 一起使用。Json
  2. 处理程序不实现 Handler,因为不可克隆 + not .selfSend
  3. 您使用的是本地 .self.routerrouter

此外,您不需要任务 - 会为您处理,您也不需要返回 - 只需返回即可。spawn()axumStatusCode::OK()

所有修复后的代码:

use axum::{
    body::Body,
    http::StatusCode,
    routing::{get, post},
    Json, Router,
};
use futures::future::BoxFuture;
use serde::Deserialize;
use std::sync::Arc;
use std::{collections::HashMap, future::Future, net::SocketAddr};

#[derive(Deserialize)]
pub struct Interaction {
    r#type: String,
}

type Shortcuts = HashMap<String, Box<dyn Fn(Interaction) -> BoxFuture<'static, ()> + Send + Sync>>;

#[derive(Default)]
pub struct App {
    shortcuts: Shortcuts,
}

impl App {
    pub fn new() -> Self {
        Self::default()
    }

    async fn handle_interaction(shortcuts: Arc<Shortcuts>, Json(interaction): Json<Interaction>) {
        let closure = shortcuts.get(&interaction.r#type).unwrap();
        closure(interaction);
    }

    pub fn add_shortcut<C, Fut>(mut self, callback_id: &str, fun: C) -> Self
    where
        Fut: Future<Output = ()> + Send + 'static,
        C: Fn(Interaction) -> Fut + Send + Sync + 'static,
    {
        self.shortcuts.insert(
            callback_id.to_string(),
            Box::new(move |interaction| Box::pin(fun(interaction))),
        );
        self
    }

    async fn start(self, addr: SocketAddr) {
        let shortcuts = Arc::new(self.shortcuts);
        let interaction_handler = move |interaction| async move {
            Self::handle_interaction(shortcuts, interaction).await
        };

        let router = Router::new().route("/", post(interaction_handler));

        axum::Server::bind(&addr)
            .serve(router.into_make_service())
            .await
            .unwrap();
    }
}

async fn shortcut1(i: Interaction) {
    println!("Hello, World!");
}

async fn shortcut2(i: Interaction) {
    println!("Hello, Other World!")
}

#[tokio::main]
async fn main() {
    // ERROR: `shortcut1` and `shortcut2` doesn't match input type,
    // and i don't know how to do this properly.
    let app = App::new()
        .add_shortcut("shortcut1", shortcut1)
        .add_shortcut("shortcut2", shortcut2);

    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    // app.start(addr).await;
}

评论

0赞 Mads Ahlquist Jensen 3/24/2023
非常感谢!起初我以为出了点问题,因为函数没有输出任何东西。然后我注意到没有等待关闭 - 它现在按预期工作!