如何将 Futures 的生命周期绑定到 Rust 中的 fn 参数
How to bind lifetimes of Futures to fn arguments in Rust
我正在尝试为 Rust MongoDB 驱动程序
编写一个简单的 run_transaction
函数
此函数尝试通过 mongo 数据库客户端执行事务并在遇到可重试错误时重试事务
这是函数的最小可重现示例。
use mongodb::{Client, Collection, ClientSession};
use mongodb::bson::Document;
use std::future::Future;
pub enum Never {}
fn main() {
run_transaction(|mut session| async move {
let document = collection().find_one_with_session(None, None, &mut session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
});
}
fn collection() -> Collection<Document> {
unimplemented!();
}
fn client() -> Client {
unimplemented!();
}
pub enum TransactionError<E> {
Mongodb(mongodb::error::Error),
Custom(E)
}
impl<T> From<mongodb::error::Error> for TransactionError<T> {
fn from(e: mongodb::error::Error) -> Self {
TransactionError::Mongodb(e)
}
}
// declaration
pub async fn run_transaction<T, E, F, Fut>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: Fn(&'a mut ClientSession) -> Fut + 'a,
Fut: Future<Output = Result<T, TransactionError<E>>> {
let mut session = client().start_session(None).await?;
session.start_transaction(None).await?;
'run: loop {
let r = f(&mut session).await;
match r {
Err(e) => match e {
TransactionError::Custom(e) => return Err(TransactionError::Custom(e)),
TransactionError::Mongodb(e) => {
if !e.contains_label(mongodb::error::TRANSIENT_TRANSACTION_ERROR) {
return Err(TransactionError::Mongodb(e));
} else {
continue 'run;
}
}
},
Ok(v) => {
'commit: loop {
match session.commit_transaction().await {
Ok(()) => return Ok(v),
Err(e) => {
if e.contains_label(mongodb::error::UNKNOWN_TRANSACTION_COMMIT_RESULT) {
continue 'commit;
} else {
return Err(TransactionError::Mongodb(e))
}
}
}
}
}
}
}
}
但是借用检查员一直抱怨这条消息:
error: lifetime may not live long enough
--> src/main.rs:8:35
|
8 | run_transaction(|mut session| async move {
| ______________________------------_^
| | | |
| | | return type of closure `impl Future` contains a lifetime `'2`
| | has type `&'1 mut ClientSession`
9 | | let document = collection().find_one_with_session(None, None, &mut session).await?.unwrap();
10 | | let r: Result<Document, TransactionError<Never>> = Ok(document);
11 | | return r;
12 | | });
| |_____^ returning this value requires that `'1` must outlive `'2`
有什么办法可以解决这个问题吗?
你真正需要的是:
pub async fn run_transaction<T, E, F, Fut>(f: F) -> Result<T, TransactionError<E>>
where
for<'a>
F: Fn(&'a mut ClientSession) -> Fut,
Fut: Future<Output = Result<T, TransactionError<E>>> + 'a {
不幸的是,这不起作用,因为 for<'a>
中定义的“高级特征绑定”(HRTB) 仅适用于下一个绑定,而不是每个绑定,并且无法连接两辈子...
但并非一切都丢失了!我在 Rust 支持论坛中发现这个 question 有类似的问题,可以根据您的问题进行调整。基本思想是创建一个特征,它用相同的生命周期包装 Fn
和 Future
边界:
pub trait XFn<'a, I: 'a, O> {
type Output: Future<Output = O> + 'a;
fn call(&self, session: I) -> Self::Output;
}
impl<'a, I: 'a, O, F, Fut> XFn<'a, I, O> for F
where
F: Fn(I) -> Fut,
Fut: Future<Output = O> + 'a,
{
type Output = Fut;
fn call(&self, x: I) -> Fut {
self(x)
}
}
现在你的绑定函数很简单了:
pub async fn run_transaction<T, E, F>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: XFn<'a, &'a mut ClientSession, Result<T, TransactionError<E>>>
请记住,要调用您必须编写的函数 f.call(&mut session)
。
不幸的是,调用run_transaction()
,照原样,不编译,说FnOnce
的实现不够通用。我认为它是 async move
的 limitation/bug 因为异步闭包不稳定。但是您可以改用适当的异步函数:
async fn do_the_thing(session: &mut ClientSession) -> Result<Document, TransactionError<Never>> {
let document = collection().find_one_with_session(None, None, session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
}
run_transaction(do_the_thing).await;
如果您认为这太复杂,并且您不介意支付非常小的运行时代价,那么有一个更简单的选择:您可以将返回的 future 装箱,完全避免第二个泛型:
pub async fn run_transaction<T, E, F>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: Fn(&'a mut ClientSession) -> Pin<Box<dyn Future<Output = Result<T, TransactionError<E>>> + 'a>>
然后,调用它:
run_transaction(|mut session| Box::pin(async move {
let document = collection().find_one_with_session(None, None, session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
}));
我正在尝试为 Rust MongoDB 驱动程序
编写一个简单的run_transaction
函数
此函数尝试通过 mongo 数据库客户端执行事务并在遇到可重试错误时重试事务
这是函数的最小可重现示例。
use mongodb::{Client, Collection, ClientSession};
use mongodb::bson::Document;
use std::future::Future;
pub enum Never {}
fn main() {
run_transaction(|mut session| async move {
let document = collection().find_one_with_session(None, None, &mut session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
});
}
fn collection() -> Collection<Document> {
unimplemented!();
}
fn client() -> Client {
unimplemented!();
}
pub enum TransactionError<E> {
Mongodb(mongodb::error::Error),
Custom(E)
}
impl<T> From<mongodb::error::Error> for TransactionError<T> {
fn from(e: mongodb::error::Error) -> Self {
TransactionError::Mongodb(e)
}
}
// declaration
pub async fn run_transaction<T, E, F, Fut>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: Fn(&'a mut ClientSession) -> Fut + 'a,
Fut: Future<Output = Result<T, TransactionError<E>>> {
let mut session = client().start_session(None).await?;
session.start_transaction(None).await?;
'run: loop {
let r = f(&mut session).await;
match r {
Err(e) => match e {
TransactionError::Custom(e) => return Err(TransactionError::Custom(e)),
TransactionError::Mongodb(e) => {
if !e.contains_label(mongodb::error::TRANSIENT_TRANSACTION_ERROR) {
return Err(TransactionError::Mongodb(e));
} else {
continue 'run;
}
}
},
Ok(v) => {
'commit: loop {
match session.commit_transaction().await {
Ok(()) => return Ok(v),
Err(e) => {
if e.contains_label(mongodb::error::UNKNOWN_TRANSACTION_COMMIT_RESULT) {
continue 'commit;
} else {
return Err(TransactionError::Mongodb(e))
}
}
}
}
}
}
}
}
但是借用检查员一直抱怨这条消息:
error: lifetime may not live long enough
--> src/main.rs:8:35
|
8 | run_transaction(|mut session| async move {
| ______________________------------_^
| | | |
| | | return type of closure `impl Future` contains a lifetime `'2`
| | has type `&'1 mut ClientSession`
9 | | let document = collection().find_one_with_session(None, None, &mut session).await?.unwrap();
10 | | let r: Result<Document, TransactionError<Never>> = Ok(document);
11 | | return r;
12 | | });
| |_____^ returning this value requires that `'1` must outlive `'2`
有什么办法可以解决这个问题吗?
你真正需要的是:
pub async fn run_transaction<T, E, F, Fut>(f: F) -> Result<T, TransactionError<E>>
where
for<'a>
F: Fn(&'a mut ClientSession) -> Fut,
Fut: Future<Output = Result<T, TransactionError<E>>> + 'a {
不幸的是,这不起作用,因为 for<'a>
中定义的“高级特征绑定”(HRTB) 仅适用于下一个绑定,而不是每个绑定,并且无法连接两辈子...
但并非一切都丢失了!我在 Rust 支持论坛中发现这个 question 有类似的问题,可以根据您的问题进行调整。基本思想是创建一个特征,它用相同的生命周期包装 Fn
和 Future
边界:
pub trait XFn<'a, I: 'a, O> {
type Output: Future<Output = O> + 'a;
fn call(&self, session: I) -> Self::Output;
}
impl<'a, I: 'a, O, F, Fut> XFn<'a, I, O> for F
where
F: Fn(I) -> Fut,
Fut: Future<Output = O> + 'a,
{
type Output = Fut;
fn call(&self, x: I) -> Fut {
self(x)
}
}
现在你的绑定函数很简单了:
pub async fn run_transaction<T, E, F>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: XFn<'a, &'a mut ClientSession, Result<T, TransactionError<E>>>
请记住,要调用您必须编写的函数 f.call(&mut session)
。
不幸的是,调用run_transaction()
,照原样,不编译,说FnOnce
的实现不够通用。我认为它是 async move
的 limitation/bug 因为异步闭包不稳定。但是您可以改用适当的异步函数:
async fn do_the_thing(session: &mut ClientSession) -> Result<Document, TransactionError<Never>> {
let document = collection().find_one_with_session(None, None, session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
}
run_transaction(do_the_thing).await;
如果您认为这太复杂,并且您不介意支付非常小的运行时代价,那么有一个更简单的选择:您可以将返回的 future 装箱,完全避免第二个泛型:
pub async fn run_transaction<T, E, F>(f: F) -> Result<T, TransactionError<E>>
where for<'a>
F: Fn(&'a mut ClientSession) -> Pin<Box<dyn Future<Output = Result<T, TransactionError<E>>> + 'a>>
然后,调用它:
run_transaction(|mut session| Box::pin(async move {
let document = collection().find_one_with_session(None, None, session).await?.unwrap();
let r: Result<Document, TransactionError<Never>> = Ok(document);
return r;
}));