簡體   English   中英

如何在容器中存儲指向異步方法的指針?

[英]How to store a pointer to an async method in a container?

我有一個定義多個async方法的結構,我想將每個方法的指針存儲在HashMap中,這樣我就可以在一行中調用任何方法,只知道參數中給出的鍵。

這里的目的是盡可能避免有一個巨大的match子句,當我向我的結構添加新方法時,它會越來越膨脹。

方法都具有相同的簽名:

async fn handle_xxx(&self, c: Command) -> Result<String, ()>

我真的很想用以下方式稱呼它們:

pub async fn execute_command(&mut self, command: Command) -> Result<String, ()> {
    let handler = self.command_callbacks.get(&command);
    let return_message: String = match handler {
        Some(f) => f(self, command).await.unwrap(), // The call is made here.
        None => return Err(()),
    };
    Ok(return_message)
}

然而,很明顯,為了在HashMap中存儲一些東西,你必須在聲明HashMap時指定它的類型,這就是麻煩開始的時候。

我嘗試了最明顯的方法,即聲明包裝函數類型:

type CommandExecutionNotWorking = fn(&CommandExecutor, Command) -> Future<Output = Result<String, ()>>;

這不起作用,因為 Future 是一種特征,而不是一種類型。

我試圖聲明一個泛型類型並在下面的某處指定它:

type CommandExecutionNotWorkingEither<Fut> = fn(&CommandExecutor, Command) -> Fut;

但是我遇到了同樣的問題,因為我需要指定Future類型,並且有一個HashMap聲明,如下所示:

let mut command_callbacks: HashMap<
    Command,
    CommandExecutionFn<dyn Future<Output = Result<String, ()>>>,
> = HashMap::new();

impl Future顯然不起作用,因為我們不在函數簽名中, Future也不是因為它不是類型,並且dyn Future創建了合法的類型不匹配。

因此,我嘗試使用Pin以便可以操縱dyn Future ,最終得到以下簽名:

type CommandExecutionStillNotWorking = fn(
    &CommandExecutor,
    Command,
) -> Pin<Box<dyn Future<Output = Result<String, ()>>>>;

但我需要操作返回Pin<Box<dyn Future<...>>>而不僅僅是Future的函數。 因此,我嘗試定義一個 lambda,它在參數中采用async函數並返回一個函數,該函數將我的async方法的返回值包裝在Pin<Box<...>>中:

let wrap = |f| {
    |executor, command| Box::pin(f(&executor, command))
};

但是編譯器並不高興,因為它希望我定義f的類型,這是我在這里試圖避免的,所以我回到了原點。

因此我的問題是:你知道是否真的可以編寫async函數的類型,以便可以像任何變量或任何其他函數指針一樣輕松地操作它們上的指針? 或者我應該選擇另一種可能不太優雅的解決方案,帶有一些重復代碼或巨大的match結構?

TL;DR:是的,有可能,但可能比你想象的要復雜。


首先,閉包不能是通用的,因此您需要一個函數:

fn wrap<Fut>(f: fn(&CommandExecutor, Command) -> Fut) -> CommandExecution
where
    Fut: Future<Output = Result<String, ()>>
{
    move |executor, command| Box::pin(f(executor, command))
}

但是您不能將返回的閉包轉換為函數指針,因為它捕獲了f

現在從技術上講它應該是可能的,因為我們只想使用函數項(非捕獲),並且它們的類型(除非轉換為函數指針)是零大小的。 所以僅通過類型我們應該能夠構造一個實例。 但這樣做需要不安全的代碼:

fn wrap<Fut, F>(_f: F) -> CommandExecution
where
    Fut: Future<Output = Result<String, ()>>,
    F: Fn(&CommandExecutor, Command) -> Fut,
{
    assert_eq!(std::mem::size_of::<F>(), 0, "expected a fn item");
    move |executor, command| {
        // SAFETY: `F` is a ZST (checked above), any (aligned!) pointer, even crafted
        // out of the thin air, is valid for it.
        let f: &F = unsafe { std::ptr::NonNull::dangling().as_ref() };
        Box::pin(f(executor, command))
    }
}

(我們需要_f作為參數,因為我們無法指定函數類型;讓推理自己找出來。)

然而,麻煩還不止於此。 他們才剛剛開始。

現在我們得到以下錯誤:

error[E0310]: the parameter type `Fut` may not live long enough
  --> src/lib.rs:28:9
   |
18 | fn wrap<Fut, F>(_f: F) -> CommandExecution
   |         --- help: consider adding an explicit lifetime bound...: `Fut: 'static`
...
28 |         Box::pin(f(executor, command))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Fut` will meet its required lifetime bounds

好吧,它提出了一個解決方案。 我們試試看...

它編譯! 成功地!!

...直到我們真正嘗試使用它:

let mut _command_callbacks: Vec<CommandExecution> = vec![
    wrap(CommandExecutor::handle_xxx),
    wrap(CommandExecutor::handle_xxx2),
];

(一個HashMap將具有相同的效果)。

error[E0308]: mismatched types
  --> src/lib.rs:34:9
   |
34 |         wrap(CommandExecutor::handle_xxx),
   |         ^^^^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&CommandExecutor, Command) -> impl Future<Output = Result<String, ()>> {CommandExecutor::handle_xxx} as FnOnce<(&CommandExecutor, Command)>>::Output`
              found associated type `<for<'_> fn(&CommandExecutor, Command) -> impl Future<Output = Result<String, ()>> {CommandExecutor::handle_xxx} as FnOnce<(&CommandExecutor, Command)>>::Output`
   = note: the required lifetime does not necessarily outlive the static lifetime
note: the lifetime requirement is introduced here
  --> src/lib.rs:21:41
   |
21 |     F: Fn(&CommandExecutor, Command) -> Fut,
   |                                         ^^^

error[E0308]: mismatched types
  --> src/lib.rs:35:9
   |
35 |         wrap(CommandExecutor::handle_xxx2),
   |         ^^^^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&CommandExecutor, Command) -> impl Future<Output = Result<String, ()>> {CommandExecutor::handle_xxx2} as FnOnce<(&CommandExecutor, Command)>>::Output`
              found associated type `<for<'_> fn(&CommandExecutor, Command) -> impl Future<Output = Result<String, ()>> {CommandExecutor::handle_xxx2} as FnOnce<(&CommandExecutor, Command)>>::Output`
   = note: the required lifetime does not necessarily outlive the static lifetime
note: the lifetime requirement is introduced here
  --> src/lib.rs:21:41
   |
21 |     F: Fn(&CommandExecutor, Command) -> Fut,
   |                                         ^^^

該問題在傳遞給異步回調的引用的生命周期中進行了描述。 解決方案是使用 trait 來解決問題:

type CommandExecution = for<'a> fn(
    &'a CommandExecutor,
    Command,
) -> Pin<Box<dyn Future<Output = Result<String, ()>> + 'a>>;

trait CommandExecutionAsyncFn<CommandExecutor>:
    Fn(CommandExecutor, Command) -> <Self as CommandExecutionAsyncFn<CommandExecutor>>::Fut
{
    type Fut: Future<Output = Result<String, ()>>;
}

impl<CommandExecutor, F, Fut> CommandExecutionAsyncFn<CommandExecutor> for F
where
    F: Fn(CommandExecutor, Command) -> Fut,
    Fut: Future<Output = Result<String, ()>>,
{
    type Fut = Fut;
}

fn wrap<F>(_f: F) -> CommandExecution
where
    F: 'static + for<'a> CommandExecutionAsyncFn<&'a CommandExecutor>,
{
    assert_eq!(std::mem::size_of::<F>(), 0, "expected a fn item");
    move |executor, command| {
        // SAFETY: `F` is a ZST (checked above), any (aligned!) pointer, even crafted
        // out of the thin air, is valid for it.
        let f: &F = unsafe { std::ptr::NonNull::dangling().as_ref() };
        Box::pin(f(executor, command))
    }
}

我不會詳細說明為什么需要這個東西或它如何解決問題。 您可以在鏈接的問題和其中鏈接的問題中找到解釋。

現在我們的代碼可以工作了。 喜歡,真的是這樣。

但是,如果您真的想要所有這些東西,請仔細考慮:只需更改函數以返回盒裝的未來可能會更容易。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM