Please help: type must outlive the static lifetime


#1

I am trying to implement std::error::Error trait, based on Error Handling section of Rust Book
https://doc.rust-lang.org/book/error-handling.html

But I can’t get the following code compiled:

playground url: https://play.rust-lang.org/?gist=d99fed5b8c13d5ad6ff6869b26dc6433&version=stable&backtrace=0

use std::error;
use std::fmt;
use std::any::Any;

#[derive(Debug)]
pub struct Err<T>(T);

impl<T:fmt::Debug+Any> error::Error for Err<T> {
  fn description(&self) -> &str { "_" }
}

impl<T: fmt::Debug> fmt::Display for Err<T> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "_") }
}

#[derive(Debug)]
pub struct Foo<'a> (Err<&'a str>);

impl<'a> fmt::Display for Foo<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "_") }
}

impl<'a> error::Error for Foo<'a> {
    fn description(&self) -> &str { "_" }

    fn cause(&self) -> Option<&error::Error> {
        // None // OK
        // Some(self) // OK
        Some(&self.0)   // error[E0477]: the type `&'a str` does not fulfill the required lifetime
                        // note: type must outlive the static lifetime
    }
}

Why I can wrap self without lifetime problem but can’t wrap &self.0 ??? why static lifetime???
How can I annotate lifetime to make it work?
Thanks!


#2

Oh, this one is kinda nasty.

First step: eliminate elision, and write the relevant lifetimes out in full.

impl<'a> error::Error for Foo<'a> {
    fn description(&self) -> &str { "_" }

    fn cause<'b>(&'b self) -> Option<&'b (error::Error + 'b)> {
        Some(&self.0)
    }
}

Error message still doesn’t change. But wait, it’s complaining about &'a str. self.0 is of type Err<&'a str>. It’s not complaining about Err<_> itself, which means it’s specifically something about the &'a str.

The error is cropping up at the moment the compiler needs to turn the &'b Err<&'a str> into a &'b (Error + 'b). If it was getting stuck on trying to prove that 'a outlives 'b, the message would be different.

Ok, no obvious issues: next step, check the docs. What does Error look like?

pub trait Error: Debug + Display + Reflect { ... }

There’s nothing in Error itself about 'static. What about its requirements? I know there’s nothing in Debug or Display that would cause issues. What’s in Reflect

pub trait Reflect { }

And at this point, you pretty much have to be psychic to know the answer: Reflect requires 'static.

So, because &'a str is being used as part of Err, and Err has to implement Error, and Error requires Reflect, and Reflect requires 'static&'a str has to outlive 'static, which it totally can’t.

Looking at how Err gets turned into Error, there’s this:

impl<T:fmt::Debug+Any> error::Error for Err<T> { ... }

And if you look up the definition of Any:

pub trait Any: 'static + Reflect { ... }

So, because &'a str is being used as part of Err, and Err has to implement Error, and Err's implementation requires T: Any, and Any requires 'static, &'a str must outlive 'static which it totally can’t.

(This time for sure!)

This kinda sucks because you don’t need Any, you need Reflect. But Reflect is an unstable implementation detail, and the only way to “get” it is to constrain on Any.

The easiest solution is to replace &'a str with String.


#3

Does it though? First, Any is Reflect: 'static suggesting Reflect itself should not be 'static? Also this is not rejected:

#![feature(reflect_marker)]

use std::marker::Reflect;

struct Foo<'a>(&'a str);

trait Bar: Reflect {}

impl<'a> Bar for Foo<'a> {}

fn main() {}

#4

You’re right. I dig some more digging and found what is totally the right explanation this time.


#5

Thank you so much for your help!:grinning: