我有一個定義多個async方法的結構,我想將每個方法的指標存盤在 a 中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)
}
然而,很明顯,為了在 a 中存盤一些東西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 Futures,最終得到以下簽名:
type CommandExecutionStillNotWorking = fn(
&CommandExecutor,
Command,
) -> Pin<Box<dyn Future<Output = Result<String, ()>>>>;
但我需要操作回傳的函式,Pin<Box<dyn Future<...>>>而不僅僅是Futures。所以我嘗試定義一個 lambda,它在引數中接受一個函式并回傳一個函式,該函式將我的方法async的回傳值包裝在 a 中:asyncPin<Box<...>>
let wrap = |f| {
|executor, command| Box::pin(f(&executor, command))
};
但是編譯器并不高興,因為它希望我定義 的型別f,這是我在這里試圖避免的,所以我回到了原點。
因此我的問題是:你知道是否真的可以撰寫async函式的型別,以便可以像任何變數或任何其他函式指標一樣輕松地操作它們上的指標?還是我應該選擇另一種可能不太優雅的解決方案,帶有一些重復代碼或龐大的match結構?
uj5u.com熱心網友回復:
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),
];
(aHashMap將具有相同的效果)。
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))
}
}
我不會詳細說明為什么需要這個東西或它如何解決問題。您可以在鏈接的問題和其中鏈接的問題中找到解釋。
現在我們的代碼可以作業了。喜歡,真的是這樣。
但是,如果您真的想要所有這些東西,請仔細考慮:只需更改函式以回傳盒裝的未來可能會更容易。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/484005.html
上一篇:*&var是多余的嗎?
