使用(可变的)借用参数调用通用异步函数
Calling a generic async function with a (mutably) borrowed argument
我的问题的最小示例。
use std::future::Future;
async fn call_changer<'a, F, Fut>(changer: F)
where
F: FnOnce(&'a mut i32) -> Fut,
Fut: Future<Output = ()> + 'a,
{
let mut i = 0;
changer(&mut i).await; // error 1
dbg!(i); // error 2
}
#[tokio::main]
async fn main() {
call_changer(|i| async move {
*i = 100;
})
.await;
}
这会导致两个相关错误,请参阅 rust playground 了解详细输出:
- 借用的时间不够长,因为
i
在 call_changer
的主体末尾被丢弃。
i
不能在 await 之后使用,因为它仍在被可变地借用。
我对两者都有点惊讶,我理解为什么 F
的 Future
return 需要与其借用 ('a
) 具有相同的生命周期 ('a
) relevant async book section)。但是,根据同一参考资料,一旦我对 changer
的结果调用等待,借用就应该结束,这显然不会发生,否则我不会有这些错误。将此示例改写为类似本书的内容,其中 changer
函数未作为参数传入,而是直接调用,按预期工作。
这是怎么回事,我能做些什么吗?用 Rc<RefCell<_>>
构造替换 &mut
可以按预期工作,但如果可能的话,我想避免这种情况。
当您将 'a
指定为通用参数时,您的意思是“我允许调用者选择它想要的任何生命周期”。例如,呼叫者也可以选择 'static
。那么你承诺通过&'a mut i32
,即&'static mut i32
。但是i
不是为了'static
而活!这就是第一个错误的原因。
第二个错误是因为您承诺要为 'a
可变借用 i
。但同样,'a
也可能覆盖整个函数,即使在您丢弃结果之后也是如此!例如,调用者可以选择 'static
,然后将引用存储在全局变量中。如果您在之后使用 i
,则在可变借用时使用它。砰!
你想要的是 不是 让调用者选择生命周期,而是说“我正在向你传递一个 some lifetime 'a
,我要你还我一个有相同lifetime的未来”。我们用来实现“我给你一些生命,但让我选择哪一个”的效果叫做HRTB(Higher-Kinded Trait Bounds)。
如果您只想 return 特定类型,而不是通用类型,它看起来像:
async fn call_changer<'a, F, Fut>(changer: F)
where
F: for<'a> FnOnce(&'a mut i32) -> &'a mut i32,
{ ... }
您也可以通过以下语法使用 Box<dyn Future>
:
use std::future::Future;
use std::pin::Pin;
async fn call_changer<F>(changer: F)
where
F: for<'a> FnOnce(&'a mut i32) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
let mut i = 0;
changer(&mut i).await;
dbg!(i);
}
#[tokio::main]
async fn main() {
call_changer(|i| {
Box::pin(async move {
*i = 100;
})
})
.await;
}
事实上,您甚至可以阅读显式 for
子句,因为 HRTB 是闭包中生命周期的默认脱糖:
where
F: FnOnce(&mut i32) -> &mut i32,
where
F: FnOnce(&mut i32) -> Pin<Box<dyn Future<Output = ()> + '_>>,
剩下的唯一问题是:我们如何用泛型表达这个 Fut
?
尝试将 for<'a>
应用于多个条件很诱人:
where
for<'a>
F: FnOnce(&'a mut i32) -> Fut,
Fut: Future<Output = ()> + 'a,
或:
where
for<'a> FnOnce(&'a mut i32) -> (Fut + 'a),
Fut: Future<Output = ()>,
但不幸的是,两者都不起作用。
我们能做什么?
一个选择是继续 Pin<Box<dyn Future>>
。
另一种是使用自定义特征:
trait AsyncSingleArgFnOnce<Arg>: FnOnce(Arg) -> <Self as AsyncSingleArgFnOnce<Arg>>::Fut {
type Fut: Future<Output = <Self as AsyncSingleArgFnOnce<Arg>>::Output>;
type Output;
}
impl<Arg, F, Fut> AsyncSingleArgFnOnce<Arg> for F
where
F: FnOnce(Arg) -> Fut,
Fut: Future,
{
type Fut = Fut;
type Output = Fut::Output;
}
async fn call_changer<F>(changer: F)
where
F: for<'a> AsyncSingleArgFnOnce<&'a mut i32, Output = ()>,
{
let mut i = 0;
changer(&mut i).await;
dbg!(i);
}
不幸的是,这不适用于闭包。我不知道为什么。你必须放一个 fn
:
#[tokio::main]
async fn main() {
async fn callback(i: &mut i32) {
*i += 100;
}
call_changer(callback).await;
}
更多信息:
- Lifetimes with async function parameters - users.rust-lang.org(特征方法来自那里)。
- Generic for FnOnce that returns a future with a lifetime.
- How to talk about lifetime of opaque FnOnce result? - internals.rust-lang.org.
我的问题的最小示例。
use std::future::Future;
async fn call_changer<'a, F, Fut>(changer: F)
where
F: FnOnce(&'a mut i32) -> Fut,
Fut: Future<Output = ()> + 'a,
{
let mut i = 0;
changer(&mut i).await; // error 1
dbg!(i); // error 2
}
#[tokio::main]
async fn main() {
call_changer(|i| async move {
*i = 100;
})
.await;
}
这会导致两个相关错误,请参阅 rust playground 了解详细输出:
- 借用的时间不够长,因为
i
在call_changer
的主体末尾被丢弃。 i
不能在 await 之后使用,因为它仍在被可变地借用。
我对两者都有点惊讶,我理解为什么 F
的 Future
return 需要与其借用 ('a
) 具有相同的生命周期 ('a
) relevant async book section)。但是,根据同一参考资料,一旦我对 changer
的结果调用等待,借用就应该结束,这显然不会发生,否则我不会有这些错误。将此示例改写为类似本书的内容,其中 changer
函数未作为参数传入,而是直接调用,按预期工作。
这是怎么回事,我能做些什么吗?用 Rc<RefCell<_>>
构造替换 &mut
可以按预期工作,但如果可能的话,我想避免这种情况。
当您将 'a
指定为通用参数时,您的意思是“我允许调用者选择它想要的任何生命周期”。例如,呼叫者也可以选择 'static
。那么你承诺通过&'a mut i32
,即&'static mut i32
。但是i
不是为了'static
而活!这就是第一个错误的原因。
第二个错误是因为您承诺要为 'a
可变借用 i
。但同样,'a
也可能覆盖整个函数,即使在您丢弃结果之后也是如此!例如,调用者可以选择 'static
,然后将引用存储在全局变量中。如果您在之后使用 i
,则在可变借用时使用它。砰!
你想要的是 不是 让调用者选择生命周期,而是说“我正在向你传递一个 some lifetime 'a
,我要你还我一个有相同lifetime的未来”。我们用来实现“我给你一些生命,但让我选择哪一个”的效果叫做HRTB(Higher-Kinded Trait Bounds)。
如果您只想 return 特定类型,而不是通用类型,它看起来像:
async fn call_changer<'a, F, Fut>(changer: F)
where
F: for<'a> FnOnce(&'a mut i32) -> &'a mut i32,
{ ... }
您也可以通过以下语法使用 Box<dyn Future>
:
use std::future::Future;
use std::pin::Pin;
async fn call_changer<F>(changer: F)
where
F: for<'a> FnOnce(&'a mut i32) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
let mut i = 0;
changer(&mut i).await;
dbg!(i);
}
#[tokio::main]
async fn main() {
call_changer(|i| {
Box::pin(async move {
*i = 100;
})
})
.await;
}
事实上,您甚至可以阅读显式 for
子句,因为 HRTB 是闭包中生命周期的默认脱糖:
where
F: FnOnce(&mut i32) -> &mut i32,
where
F: FnOnce(&mut i32) -> Pin<Box<dyn Future<Output = ()> + '_>>,
剩下的唯一问题是:我们如何用泛型表达这个 Fut
?
尝试将 for<'a>
应用于多个条件很诱人:
where
for<'a>
F: FnOnce(&'a mut i32) -> Fut,
Fut: Future<Output = ()> + 'a,
或:
where
for<'a> FnOnce(&'a mut i32) -> (Fut + 'a),
Fut: Future<Output = ()>,
但不幸的是,两者都不起作用。
我们能做什么?
一个选择是继续 Pin<Box<dyn Future>>
。
另一种是使用自定义特征:
trait AsyncSingleArgFnOnce<Arg>: FnOnce(Arg) -> <Self as AsyncSingleArgFnOnce<Arg>>::Fut {
type Fut: Future<Output = <Self as AsyncSingleArgFnOnce<Arg>>::Output>;
type Output;
}
impl<Arg, F, Fut> AsyncSingleArgFnOnce<Arg> for F
where
F: FnOnce(Arg) -> Fut,
Fut: Future,
{
type Fut = Fut;
type Output = Fut::Output;
}
async fn call_changer<F>(changer: F)
where
F: for<'a> AsyncSingleArgFnOnce<&'a mut i32, Output = ()>,
{
let mut i = 0;
changer(&mut i).await;
dbg!(i);
}
不幸的是,这不适用于闭包。我不知道为什么。你必须放一个 fn
:
#[tokio::main]
async fn main() {
async fn callback(i: &mut i32) {
*i += 100;
}
call_changer(callback).await;
}
更多信息:
- Lifetimes with async function parameters - users.rust-lang.org(特征方法来自那里)。
- Generic for FnOnce that returns a future with a lifetime.
- How to talk about lifetime of opaque FnOnce result? - internals.rust-lang.org.