运行 带有 Rust 期货的异步可变操作

Running asynchronous mutable operations with Rust futures

我正在使用 tokio-rs 在 Rust 中构建服务,到目前为止我对这个技术堆栈很满意。我现在正在尝试链接包括写入在内的异步操作,并且很难使用借用检查器。

我简化的最小代码示例是这样的:

extern crate futures; // 0.1.21

use futures::Future;
use std::{cell::RefCell, rc::Rc};

trait RequestProcessor {
    fn prepare(&self) -> Box<Future<Item = (), Error = ()>>;
    fn process(&mut self, request: String) -> Box<Future<Item = (), Error = ()>>;
}

struct Service {
    processor: Rc<RefCell<RequestProcessor>>,
}

impl Service {
    fn serve(&mut self, request: String) -> Box<Future<Item = (), Error = ()>> {
        let processor_clone = self.processor.clone();
        let result_fut = self
            .processor
            .borrow()
            .prepare()
            .and_then(move |_| processor_clone.borrow_mut().process(request));
        Box::new(result_fut)
    }
}

fn main() {}

作为一个简短的总结,在异步准备步骤之后,我正在尝试 运行 另一个写入字段 self 的异步操作。如果没有可变性,这很容易与普通 Rc 成员一起工作,但可变性会破坏它,产生以下错误:

error[E0597]: `processor_clone` does not live long enough
  --> src/main.rs:22:32
   |
22 |             .and_then(move |_| processor_clone.borrow_mut().process(request));
   |                                ^^^^^^^^^^^^^^^                             - `processor_clone` dropped here while still borrowed
   |                                |
   |                                borrowed value does not live long enough
   |
   = note: values in a scope are dropped in the opposite order they are created

我希望这应该有效,但我没有看到仍然从哪里借用了可变引用。我认为process()应该在返回future后释放处理器的&mut self,所以应该不会出现编译错误。

能否请您解释一下根本原因?这个例子应该如何修改才能被编译器接受?

有时,如果将值拆分为多行,则更容易看到生命周期错误。让我们尝试一下有问题的闭包:

.and_then(move |_| {
    let c = processor_clone;
    let mut d = c.borrow_mut();
    let e = d.process(request);
    e
});

如果你编译它……它会起作用。如果我们尝试重新组合这些行,我们可以让它失败:

.and_then(move |_| {
    let c = processor_clone;
    c.borrow_mut().process(request)
});

这是有效的:

.and_then(move |_| {
    let c = processor_clone;
    return c.borrow_mut().process(request);
});

唯一的区别是明确的 return 和分号。这与 When returning the outcome of consuming a StdinLock, why was the borrow to stdin retained?, so let's try the suggestion of one of the answers to enable non-lexical lifetimes 非常相似。这也允许您的原始代码进行编译。

TL;DR:这是 Rust 当前借用检查器实现中的一个弱点,将在某个时候神奇地修复。同时,我建议将其写成两行:

.and_then(move |_| {
    let mut c = processor_clone.borrow_mut();
    c.process(request)
});