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>)
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.This is the first thing that might be confusing:
try!
callsstd::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 ofInto<Box<Error>>
forSomeError
. Through the magicimpl<T, U> Into<U> for T where U: From<T>
indirection, the compiler finds a few implementations that might work out, notably this one: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 traitError
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 forBox<Trait>
the lifetime bound'static
is added. Thus the return type explicitly written out isResult<(), Box<Error + 'static>>
.To make your code compile, add an explicit lifetime to the input parameter and to the output type, like so:
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.