简体   繁体   中英

lifetime bound on associated type is rejected although it seems valid

I have a piece of code that does not compile and that can be reduced to this snippet:

use std::error::Error;
use std::convert::TryFrom;

// A trait that provides methods for parsing data into a type T.
pub trait Deserializable<T> {
    // some methods
}

pub struct MyBuffer<'a> {
    inner: &'a [u8]
}

impl<'a, T> Deserializable<T> for MyBuffer<'a> 
where
    T: TryFrom<&'a [u8]>,
    <T as TryFrom<&'a [u8]>>::Error: Error + Sync + Send + 'static 
{
    // some methods to implement
}

fn main() {}

The compiler rejects this program with a confusing error message:

error[E0310]: the associated type `<T as std::convert::TryFrom<&[u8]>>::Error` may not live long enough
  --> src/main.rs:13:13
   |
13 | impl<'a, T> Deserializable<T> for MyBuffer<'a> 
   |             ^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `<T as std::convert::TryFrom<&[u8]>>::Error: 'static`...
note: ...so that the type `<T as std::convert::TryFrom<&[u8]>>::Error` will meet its required lifetime bounds
  --> src/main.rs:13:13
   |
13 | impl<'a, T> Deserializable<T> for MyBuffer<'a> 
   |             ^^^^^^^^^^^^^^^^^

The error suggests that I add the 'static lifetime bound, but I already added it:

consider adding an explicit lifetime bound `<T as std::convert::TryFrom<&[u8]>>::Error: 'static`

Can someone explain why this program does not compile, and/or how to fix it (if possible)? It seems to me that it should be possible to have <T as TryFrom<&'a [u8]>>::Error to be 'static even though T itself is bound to 'a .

The reason I want Error to be 'static is that I use failure and failure::Fail is implemented for Send + Sync + Error + 'static .

This appears to be a gap in the compiler's ability to reason about lifetimes and associated types.

Sometimes, in order to help the compiler, you can add a generic parameter to alias an associated type. This parameter doesn't "count" in that it doesn't make the item "more generic", but because generics are resolved at use instead of declaration, it defers the hard part of type checking until the exact types are known. In other words: the compiler might be able to prove that any particular T::Error works, but it can't quite prove that every T::Error must work, so we introduce a new parameter E which is bound to be T::Error and tell the compiler to figure out what E is only when we try to use it.

The following works ( playground ):

impl<'a, T, E> Deserializable<T> for MyBuffer<'a>
where
    T: TryFrom<&'a [u8], Error = E>,
    E: Error + Sync + Send + 'static,
{
    // ...
}

Instead of bounding T::Error , we introduce a new type parameter E with the bounds we want and constrain T such that its TryFrom::Error is E . This is logically (as far as I can tell) the same thing as what you wrote, but it compiles without complaint.

I can't find official documentation that refers to this; it may be an inherent limitation of the solver, or merely a bug.

I'm still not sure why , but it seems just removing the 'static bound on the error worked?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d4f46a0ad9a5dd7dc538fe4e197d823d

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM