如何将期货的寿命与铁锈中的Fn论点捆绑在一起 [英] How to bind lifetimes of Futures to fn arguments in Rust

查看:0
本文介绍了如何将期货的寿命与铁锈中的Fn论点捆绑在一起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为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 Support论坛中找到了这个question,它有类似的问题,可以根据您的问题进行调整。基本思想是创建一个特征,用相同的生命周期包装FnFuture边界:

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的一个限制/错误,因为异步关闭是不稳定的。但您可以使用适当的异步函数:

    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;

如果您认为这太复杂,并且您不介意支付非常小的运行时代价,则有一个更简单的选择:您可以将返回的未来装箱,完全避免第二个泛型:

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;
    }));

这篇关于如何将期货的寿命与铁锈中的Fn论点捆绑在一起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆