Where did the 'static lifetime come from

2019-02-19 14:24发布

问题:

I've got the following code (doesn't make much sense, just a minimised test case):

extern crate rustc_serialize;

use rustc_serialize::json::Json;
use std::error::Error;

struct SomeStruct;

#[derive(Debug)]
enum SomeError<'a> {
    Something(&'a str),
    Other,
}

fn do_stuff(doc: &Json) -> Result<SomeStruct, SomeError> {
    Ok(SomeStruct)
}

fn get_things(doc: &Vec<Json>) -> Result<SomeStruct, Box<Error>> {
    let res = try!(doc.get(0).ok_or(SomeError::Other));
    Ok(try!(do_stuff(&res)))                             //// line 20
}

fn main() {
    let _ = get_things(&vec!(Json::Null));
}

impl<'a> std::fmt::Display for SomeError<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(f, "blah")
    }
}
impl<'a> Error for SomeError<'a> {
    fn description(&self) -> &str { "blah" }
}

This fails with a type mismatch at line 20: expected std::result::Result<SomeStruct, Box<std::error::Error + 'static>>, found std::result::Result<SomeStruct, Box<std::error::Error>>

I don't understand where did the 'static lifetime requirement come from suddenly. If I change the enum to use Something(&'static str) it works just fine, but why can't I use a less restrictive value here?

The error mentiones that doc is the borrowed content that the error cannot outlive... but there doesn't seem to be any relation between those two types.

Full error:

error: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements [E0495]
  --> src/main.rs:19:24
19 |>     let res = try!(doc.get(0).ok_or(SomeError::Other));
   |>                        ^^^
src/main.rs:19:15: 19:55: note: in this expansion of try! (defined in <std macros>)
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the block at 18:65...
  --> src/main.rs:18:66
18 |> fn get_things(doc: &Vec<Json>) -> Result<SomeStruct, Box<Error>> {
   |>                                                                  ^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:19:20
19 |>     let res = try!(doc.get(0).ok_or(SomeError::Other));
   |>                    ^^^
src/main.rs:19:15: 19:55: note: in this expansion of try! (defined in <std macros>)
note: but, the lifetime must be valid for the static lifetime...
note: ...so that types are compatible (expected std::result::Result<SomeStruct, Box<std::error::Error + 'static>>, found std::result::Result<SomeStruct, Box<std::error::Error>>)
 --> <std macros>:5:8
5 |> return $ crate :: result :: Result :: Err (
  |>        ^
src/main.rs:20:8: 20:28: note: in this expansion of try! (defined in <std macros>)

回答1:

It's quite a bit going on here. In order to explain it, let's look at this even more simplified version of your problem. To avoid hiding things, I also replaced the try!() with its explicit form.

enum SomeError<'a> {
    Something(&'a str),
    Other,
}

// ... impls Debug, Display, Error for SomeError ...

fn do_stuff(doc: &u32) -> Result<(), SomeError> { Ok(()) }

fn get_things(doc: &Vec<u32>) -> Result<(), Box<Error>> {
    match do_stuff(&v[0]) {   // `try!` expands into this match 
        Ok(v) => Ok(v),
        Err(e) => Err(e.into()),
    }  //             ^^^^^^^^--- #1

}

fn main() {
    let _ = get_things(&vec![]);
}

This is the first thing that might be confusing: try! calls std::convert::From::from(e) (or equivalent, but shorter: e.into()). This means that the interesting spot is only the part marked with #1.

So what is going on there?

Calling into() means that the Rust compiler has to search for some implementation of Into<Box<Error>> for SomeError. Through the magic impl<T, U> Into<U> for T where U: From<T> indirection, the compiler finds a few implementations that might work out, notably this one:

impl<'a, E: Error + 'a> From<E> for Box<Error + 'a>

Here we see another key point: the type Box<Error + 'a> has a lifetime bound in it. The type in plain English would read something like: "a boxed type that implements the trait Error and is alive at least for the lifetime 'a".

And now we look at our function signature and see Result<(), Box<Error>>: it doesn't have a lifetime bound in it! Well... we didn't explicitly write one out, but the compiler adds one anyway, because it's the only way to work with such boxed traits. This RFC is about default lifetime bounds that the compilers adds automagically. And for Box<Trait> the lifetime bound 'static is added. Thus the return type explicitly written out is Result<(), Box<Error + 'static>>.

To make your code compile, add an explicit lifetime to the input parameter and to the output type, like so:

fn get_things<'a>(doc: &'a Vec<u32>) -> Result<(), Box<Error + 'a>> {
    ...
}

That way you can avoid the 'static lifetime bound added by default and tell the compiler that the thing in your box only has to life as long as your input parameter.



标签: rust