2

I'm trying to build a very basic example of an asynchronous callback function in Rust:

extern crate tokio;
extern crate tokio_core;
extern crate tokio_io;

use std::error::Error;
use tokio::prelude::future::ok;
use tokio::prelude::Future;

fn async_op() -> impl Future<Item = i32, Error = Box<Error>> {
    ok(12)
}

fn main() {
    let fut = async_op().and_then(|result| {
        println!("result: {:?}", result);
    });
    tokio::run(fut);
}

This always results in the compiler error:

error[E0106]: missing lifetime specifier
 --> src/main.rs:9:54
  |
9 | fn async_op() -> impl Future<Item = i32, Error = Box<Error>> {
  |                                                      ^^^^^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
  = help: consider giving it a 'static lifetime

Why is there a lifetime error in the first place? Why is it only for the Error but not for the Item?

I am also unsure about "help: consider giving it a 'static lifetime" ‒ AFAICT this would result in a lifetime of the return value over the entire program execution, which is definitely not what I would want in a more complex example.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
traceroute
  • 21
  • 1

1 Answers1

3

Everything in Rust has a lifetime bound. If it contains references, it is the lifetime of the shortest-lived reference, otherwise it is 'static, which should be interpreted as “does not depend on anything that could have shorter lifetime”.

Therefore the lifetime of i32 is known. It is 'static, because it does not contain any references.

But Error, which is std::error::Error is a trait, and one which does not require any lifetime bound. That means you could implement it for a reference, or a type including a reference with a lifetime. And since the code is supposed to be valid for any substitution you might make anywhere downstream, the compiler insists that you give it a lifetime that is lower bound for usability of the return value.

Giving it 'static lifetime is sensible here. It does not mean the return value would be valid over the entire program execution, it only means the the return value is not limited to any shorter lifetime (which basically means that it does not depend on anything outside the Box except possibly static things) and will stay valid as long as something holds on to it.

I believe the correct syntax is:

fn async_op() -> impl Future<Item = i32, Error = Box<Error + 'static>>

Note that it is really only limiting the content of the Box. That's the only thing the compiler does not see into and is concerned it might cease to be valid at some point. And thus you promise it that the content of the Box won't become invalid until it drops the Box.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • *the life of the return value can be extended* — this is very dangerous wording. Many newcomers to Rust think that lifetimes *control* how long something lives and that by finding the "magic" lifetime that their code will start working. Lifetimes do not work this way, but the term "extend" is commonly used by those who think they do. – Shepmaster Aug 20 '18 at 13:21
  • 1
    @Shepmaster, I tried to reword it. The lifetime is indeed merely an upper bound on the validity. – Jan Hudec Aug 20 '18 at 17:58
  • I don't think I stated this clearly, but I knew that you knew the right thing; it's just mostly a matter of tricky phrasing. ;-) – Shepmaster Aug 20 '18 at 18:00