Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running asynchronous mutable operations with Rust futures

I'm building a service in Rust using tokio-rs and was happy with this tech stack so far. I'm now trying to chain up async operations that includes writes and having a hard time with the borrow checker.

My simplified minimal code sample is this:

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() {}

As a short summary, after an async preparation step I'm trying to run another async operation that writes a field of self. Without mutability this works easily with a plain Rc member, but mutability breaks it, producing the following error:

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

I'd expect this should work, I don't see where the mutable reference is still borrowed. I think process() should release the processor's &mut self after the future is returned, so the compile error shouldn't occur.

Could you please explain the underlying reasons? How should this example be changed to be accepted by the compiler?

like image 764
Zólyomi István Avatar asked May 17 '18 12:05

Zólyomi István


1 Answers

Sometimes, it's easier to see lifetime errors if you split values up onto multiple lines. Let's try that with the closure in question:

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

If you compile this... it works. If we try recombining the lines, we can get this to fail:

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

And this to work:

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

The only difference is the explicit return and the semicolon. This is very similar to 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. This allows your original code to compile as well.

TL;DR: It's a weakness in Rust's current borrow checker implementation and will be magically fixed at some point. In the meantime, I suggest writing it as two lines:

.and_then(move |_| {
    let mut c = processor_clone.borrow_mut();
    c.process(request)
});
like image 118
Shepmaster Avatar answered Nov 15 '22 11:11

Shepmaster