Search code examples
rustfutureassociated-types

Lifetime compiler error on operation returning Box<Future>


I was experimenting with asynchronous operations using the futures with tokio crate and had no problems. Now I'm implementing a storage that loads up data asynchronously and then performs some transformations, but my interface seem to have some lifetime problem which I fail to understand.

This is a reduced test code showing the same symptoms; the real function bodies perform more reasonable actions than always returning errors:

extern crate futures; // v0.1 (old)

use futures::prelude::*;
use futures::future;

pub enum MyError {
    SomeError,
}

pub trait KeyValueStore {
    type ValueType;

    fn load(&self, key: String) -> Box<Future<Item = Vec<u8>, Error = MyError>>;
    fn deserialize(&self, serialized_obj: Vec<u8>) -> Result<Self::ValueType, MyError>;

    fn resolve(&self, key: String) -> Box<Future<Item = Self::ValueType, Error = MyError>>;
}

pub struct Storage<Obj> {
    _unused: std::marker::PhantomData<Obj>,
}

impl<Obj: 'static> KeyValueStore for Storage<Obj> {
    type ValueType = Obj;

    fn deserialize(&self, serialized_obj: Vec<u8>) -> Result<Self::ValueType, MyError> {
        Err(MyError::SomeError)
    }

    fn load(&self, key: String) -> Box<Future<Item = Vec<u8>, Error = MyError>> {
        Box::new(future::err(MyError::SomeError))
    }

    fn resolve(&self, key: String) -> Box<Future<Item = Self::ValueType, Error = MyError>> {
        let result = self.load(key).and_then(|bytes| self.deserialize(bytes));
        Box::new(result)
    }
}

The compiler rejects this sample code with the following lifetime problem:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:36:9
   |
36 |         Box::new(result)
   |         ^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 34:5...
  --> src/main.rs:34:5
   |
34 | /     fn resolve(&self, key: String) -> Box<Future<Item = Self::ValueType, Error = MyError>> {
35 | |         let result = self.load(key).and_then(|bytes| self.deserialize(bytes));
36 | |         Box::new(result)
37 | |     }
   | |_____^
note: ...so that the type `futures::AndThen<std::boxed::Box<futures::Future<Error=MyError, Item=std::vec::Vec<u8>>>, std::result::Result<Obj, MyError>, [closure@src/main.rs:35:46: 35:77 self:&&Storage<Obj>]>` will meet its required lifetime bounds
  --> src/main.rs:36:9
   |
36 |         Box::new(result)
   |         ^^^^^^^^^^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that expression is assignable (expected std::boxed::Box<futures::Future<Error=MyError, Item=Obj> + 'static>, found std::boxed::Box<futures::Future<Error=MyError, Item=Obj>>)
  --> src/main.rs:36:9
   |
36 |         Box::new(result)
   |         ^^^^^^^^^^^^^^^^

Previous code I have written with hyper or tokio-postgres seemingly had the same logic but never produced such errors. I can't even see where the lifetime could go wrong here without references. My gut feeling says it's somehow related to the generic Obj parameter, the static lifetime restriction doesn't feel right.

What circumstances cause the compile error?


Solution

  • The self in self.deserialize needs the lifetime from the &self parameter to resolve; the result cannot be boxed as Box<Future<..>>, which requires a 'static lifetime for the boxed Future.

    You could override the lifetime requirement for the boxed future with Box<Future<..> + 'a> (where 'a is the lifetime of the self parameter; you'd need to change the resolve signature in the trait and in the implementation); but there is not much you can do with the result, as the usual Future-based event loops will require a 'static lifetime for the Futures they have to run.

    Instead you can solve this by making deserialize a "static method", i.e. removing the &self argument and calling it through Self::deserialize instead.

    The default lifetime requirement (in this case 'static) when using traits as types is documented in Trait objects in the reference.