两次借用可变变量的递归异步函数

时间:2021-04-24 04:08:44

标签: rust

我正在设计一个连接重试功能。如果错误是 ConnectionClosed,它会重试。在完整的实现中,它还有其他东西证明它是递归的,但这是题外话。无论如何,因为它是一个连接,所以异步是有意义的。但是,异步递归迫使我使用 BoxFuture,我们知道异步捕获并返回所有参数。

use std::future::Future;
use futures::future::{BoxFuture, FutureExt};

struct Message;

struct Client;

enum Error {
    ConnectionClosed
}

impl Client {
    fn send_and_expect<'a>(
        &'a mut self,
        message: &'a Message
    ) -> BoxFuture<'a, Result<(), Error>> {
        async move {
            Ok(())
        }.boxed()
    }
    
    //With this function I can wrap any async function f that grabs data from the internet
    pub fn connection_retrier<'a, T>(
        f: fn(&'a mut Self, &'a Message) -> T,
        f_self: &'a mut Self,
        f_m: &'a Message,
    ) -> BoxFuture<'a, Result<(),Error>>
    where
        T: Future<Output = Result<(), Error>> + 'a + Send
    {
        /*
            By making f: fn(&'a mut Self, &'a Message) -> T, we tell the compiler that
            `f` is a specific function: one that specifically requires the `Self` and `Message` 
            arguments to live as long as `'a`, where `'a` is the min of the lifetimes of the `f_self` and `f_m`
            arguments passed to `connection_retrier`.
            Thus this forces `f_self` and `f_m` to live as long as `connection_retrier`.
            The call `let r = f(f_self, f_m).await` returns a `Future` that lives as long as `'a`.
            I think this is the problem. The result of the first call borrows `f_self` and `f_m`
            for at least the entire lifetime of `r`. This would make it impossible to use
            `f_self` and `f_m` again inside `connection_retrier`. As you see, I tried making sure
            that `r` destructs before we use `f_self` in `connection_retrier` again, but somehow
            `r` is marked to live as long as `connection_retrier`, because `f_self` is still considered
            to be borrowed.
        */
        async move {
            let ok: bool; 
            {
                let r = f(f_self, f_m).await;
                match r {
                    Ok(_) => ok = true,
                    Err(Error::ConnectionClosed) => {
                        ok = false;
                    }
                }
            }
            match ok {
                true => Ok(()),
                false => Client::connection_retrier(f, f_self, f_m).await
            }
        }.boxed()
    }
    
    async fn send_with_retry<'a> (
        &'a mut self,
        message: &'a Message,
    ) -> Result<(), Error>
    {
        Client::connection_retrier(
            Client::send_and_expect,
            self,
            message
        ).await
    }
}

错误:

error[E0499]: cannot borrow `*f_self` as mutable more than once at a time
  --> src/lib.rs:58:56
   |
24 |         f: fn(&'a mut Self, &'a Message) -> T,
   |         - lifetime `'1` appears in the type of `f`
...
48 |                 let r = f(f_self, f_m).await;
   |                         --------------
   |                         | |
   |                         | first mutable borrow occurs here
   |                         argument requires that `*f_self` is borrowed for `'1`
...
58 |                 false => Client::connection_retrier(f, f_self, f_m).await
   |                                                        ^^^^^^ second mutable borrow occurs here

Playground

我理解错误发生的原因:Client::connection_retrier(f, f_self, f_m).await,我们称之为 r,持有对 f_self 的可变引用,因此在持有它时我无法再次使用它。但是,在我检查这个结果 rError::ConnectionClosed 之后,我不再需要它了,所以应该有一种方法可以丢弃它,以便我可以可变地重新借用它。

2 个答案:

答案 0 :(得分:1)

原因就在这三行中:

pub fn connection_retrier<'a, T>(
    f: fn(&'a mut Self, &'a Message) -> T,
    f_self: &'a mut Self,

对于 f,您告诉编译器它将收到一个可变引用,其生命周期等于 connection_retrier 为 {{1} 接收的可变引用的生命周期}}(在本例中为 f_self)。

当您将 'a 的签名更改为 f 时,您是在让编译器确定生命周期,它通过为它分配一个的生命周期而正确地做到了这一点{{ 1}} 以及为什么它会编译。

答案 1 :(得分:1)

问题

正如@moy2010 所说,问题出在这里:

pub fn connection_retrier<'a, T>(
        f: fn(&'a mut Self, &'a Message) -> T,
        f_self: &'a mut Self,
        f_m: &'a Message,
    ) -> BoxFuture<'a, Result<(), Error>>
where T: Future<Output = Result<(), Error>> + 'a + Send

此签名将 f 定义为一个函数,它接受具有恰好生命周期 'a 的参数,该生命周期在 connection_retrier 的整个主体中都处于活动状态。 >

当您能够临时将可变引用传递给函数,然后稍后“恢复”它时,这称为重新借用。从可变引用重新借用它的工作原理是为它指向的数据创建一个生命周期较短的新可变引用,并在生命周期结束之前阻止使用原始引用。通常在函数调用期间会发生重新借用,在这种情况下,生命周期刚好足以进行函数调用。

如您所见,再借用总是会产生一个生命周期的引用。但是 f 需要一个生命周期正好是 'a 的引用,所以编译器正确地推断出对 f 的调用不能重新借用 f_self,相反,f_self 是永久的搬入f。因此,当您再次尝试使用它时会出现错误。

解决方案

如果您可以更改 send_and_expect 的签名,以便返回的 Future onlymessage 借用(即 不是 {{1} }),那么@moy2010 已经在他们的答案的评论中提供了正确的解决方案。我将继续假设 Client 必须从它的两个参数中借用,如问题中的签名所示。

由于您想在将 send_and_expect 传递给 f_self 之前重新借用它,因此您真正需要的是将 f 定义为一个可以为 any< 调用的函数/em> 生命周期(不仅仅是 f)。您可能认为这意味着我们可以将 'a 的声明更改为 f。可是等等!您的 for<'b> fn(&'b mut Self, &'b Message) -> T 边界指定 where 从生命周期 T 中借用,而不是 'a,因此此签名与 'b 的签名不一致。但也请注意,我们不能更改 send_and_expect 的绑定,因为 'b 不在 'b 声明之外的范围内! 所以这里没有办法在 f 上表达正确的界限。但是,有一些解决方法。

使用 Ted trait 对象

最简单的解决方案就是让 Box 的返回类型成为一个装箱的 trait 对象而不是一个泛型参数,就像这样:

f

现在我们可以指定返回值从内联借用什么,避免 pub fn connection_retrier<'a>( f: for<'b> fn(&'b mut Self, &'b Message) -> BoxFuture<'b, Result<(), Error>>, f_self: &'a mut Self, f_m: &'a Message, ) -> BoxFuture<'a, Result<(), Error>> 不在 'b 范围内的问题。由于 where 在您的示例中无论如何已经返回 send_and_expect,因此这可能是您的最佳解决方案。

使用在 ZST 上实现的特征而不是 BoxFuture 指针

求助于装箱和动态分派来解决编译时问题可能会让一些人感到不快,并且会造成很小的性能损失。这是一个技巧,可用于通过静态调度使其工作,但代价是有些冗长。我们必须定义自己的特征,fn

ConnectFn

然后,我们可以使 trait ConnectFn<'a> { type ConnectFuture: Future<Output=Result<(), Error>> + Send + 'a; fn connect(&self, client: &'a mut Client, message: &'a Message) -> Self::ConnectFuture; } 的签名如下:

connection_retrier

并将正文中的 pub fn connection_retrier<'a, F>( f: F, f_self: &'a mut Self, f_m: &'a Message, ) -> BoxFuture<'a, Result<(), Error>> where F: for<'b> ConnectFn<'b> 替换为 f(f_self, f_m)。这避免了 f.connect(f_self, f_m) 的返回类型出现在 f 签名中的任何位置的需要,从而避免了我们之前遇到的问题。

然后我们可以用实现 connection_retrier 的零大小类型 send_and_expect 替换我们的函数 SendAndExpect,如下所示:

ConnectFn

有了这个,我们可以像这样调用struct SendAndExpect; impl<'a> ConnectFn<'a> for SendAndExpect { type ConnectFuture = BoxFuture<'a, Result<(), Error>>; fn connect(&self, client: &'a mut Client, message: &'a Message) -> Self::ConnectFuture { /* the body of Client::send_and_expect goes here. `self` is ignored */ } }

connection_retrier

这很不错。 Client::connection_retrier(SendAndExpect, &mut client, &message) 本身的定义可以使用宏变得更好,接近于常规 SendAndExpect 的定义,但这留给读者作为练习。

旁注:您可能认为可以在合适的 fn 指针上全面实现 ConnectFn 并避免对新类型的需要,但遗憾的是您错了。在处理通用量化类型时,您会遇到同样的问题,即不可能的 fn 子句边界或特征求解器限制。