2

I'm trying to use a struct created in main() and pass it on to a function that returns a boxed Future. However, I run into lifetime and borrowing issues and can't seem to resolve this cleanly.

Here is my struct and functions:

extern crate futures; // 0.1.21
extern crate tokio_core; // 0.1.17

use futures::{future::ok, Future};

pub struct SomeStruct {
    some_val: u32,
}

impl SomeStruct {
    pub fn do_something(&self, value: u32) -> u32 {
        // Do some work
        return self.some_val + value;
    }
}

fn main() {
    let core = tokio_core::reactor::Core::new().unwrap();
    let my_struct = SomeStruct { some_val: 10 };

    let future = get_future(&my_struct);
    core.run(future);

    let future2 = get_future(&my_struct);
    core.run(future2);
}

fn get_future(some_struct: &SomeStruct) -> Box<Future<Item = u32, Error = ()>> {
    let fut = ok(20).and_then(|val| {
        let result = some_struct.do_something(val);
        ok(result)
    });
    Box::new(fut)
}

On compiling, the following error occurs:

error[E0621]: explicit lifetime required in the type of `some_struct`
  --> src/main.rs:33:5
   |
28 | fn get_future(some_struct: &SomeStruct) -> Box<Future<Item = u32, Error = ()>> {
   |               ----------- consider changing the type of `some_struct` to `&'static SomeStruct`
...
33 |     Box::new(fut)
   |     ^^^^^^^^^^^^^ lifetime `'static` required

I suppose the error occurs because SomeStruct is used in the Future and might be used outside of main()s scope, hence the compiler asks me to change the lifetime to 'static. Here is what I tried so far (unsuccessfully):

  • Changing the lifetime to 'static as suggested by the compiler, which creates borrowing issues in main().
  • Moving val by adding ok(20).and_then(move |val| { as suggested by the compiler, which creates issues in the second invocation of get_future().
  • Use the lazy_static crate to explicitly initialize SomeStruct as static (as suggested here), however I run into macro errors when trying that.

The whole example can be found here. I have omitted some details to create an minimal example. The issues occur using tokio-core and futures = "0.1". Migrating to version "0.2" is not an option unfortunately, due to a dependency of another library.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
flock0
  • 33
  • 1
  • 4
  • It's not the argument `val` that gets moved when you make a `move` closure; it's the environment (in this case represented by `some_struct`). – trent Jun 18 '18 at 18:24

1 Answers1

4

Returning a boxed trait object has a 'static bound by default. Do as the compiler suggests and provide an explicit lifetime, but not 'static:

fn get_future<'a>(some_struct: &'a SomeStruct) -> Box<dyn Future<Item = u32, Error = ()> + 'a> {
    let fut = future::ok(20).and_then(move |val| {
        let result = some_struct.do_something(val);
        future::ok(result)
    });
    Box::new(fut)
}

You also have to use move to transfer ownership of some_struct to the closure and change core to be mutable. You should also handle potential errors resulting from core.run.

For the example provided, you could also return impl Future:

fn get_future<'a>(some_struct: &'a SomeStruct) -> impl Future<Item = u32, Error = ()> +'a {
    future::ok(20).and_then(move |val| {
        let result = some_struct.do_something(val);
        future::ok(result)
    })
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    I was stuck fighting the borrow checker and was about ready to give up on Rust. And then read this answer when looking for some other issue... This is the answer that made it click for me. Thanks! – Dave May 03 '19 at 10:00