Inverted lifetime parameter for a trait


#1

I ran into some trouble while working on zero-copy deserialization for Serde: serde-rs/serde#492.

We have a trait Deserialize<'de> that behaves like the Put<'a> trait below. Importantly, I need a function that requires T: Put<'static> to be callable with any T: Put<'a> which is the inverse of the usual relationship for lifetimes. Rust seems to handle this great for struct lifetimes but not trait lifetimes as demonstrated in the following code.

I found this relevant sentence in the Rustonomicon:

Traits don’t have inferred variance, so Fn(T) is invariant in T.

Has anyone encountered this limitation in other contexts or developed any approaches for working around it?


#![allow(dead_code, unused_variables)]

// Covariance / contravariance is inferred correctly for struct lifetimes.
mod with_struct {
    struct Get<'a> {
        get: fn() -> &'a str,
    }
    fn get_any<'a>(x: Get<'a>) {
        fn get_static(x: Get<'static>) {
            let _: &'static str = (x.get)();
        }
        // Not allowed. Makes sense.
        //get_static(x);
    }

    struct Put<'a> {
        put: fn(&'a str),
    }
    fn put_any<'a>(x: Put<'a>) {
        fn put_static(x: Put<'static>) {
            (x.put)("static");
        }
        // Allowed. Perfect.
        put_static(x);
    }
}

// Not for trait lifetimes.
mod with_trait {
    trait Get<'a> {
        fn get() -> &'a str;
    }
    fn get_any<'a, T: Get<'a>>() {
        fn get_static<T: Get<'static>>() {
            let _: &'static str = T::get();
        }
        // Not allowed. Makes sense.
        //get_static::<T>();
    }

    trait Put<'a> {
        fn put(&'a str);
    }
    fn put_any<'a, T: Put<'a>>() {
        fn put_static<T: Put<'static>>() {
            T::put("static");
        }
        // Not allowed. But should be?
        put_static::<T>();
    }
}

#2

Hmm, that’s unfortunate, as it is this won’t work. What’s the rationale for the T: Put<'static> function? I guess the idea is to keep the variance on the trait the same as the function, but since you can give a Put<'a> a &'static str why do you need to explicitly require T: Put<'static>?


#3

:anguished: I forgot to loop back on this thread.

Thanks @KodrAus, that helped shape the recommendations I give in https://serde.rs/lifetimes.html. In particular, Deserialize<'static> is probably never a trait bound you would want.

In my early experience with deserializer lifetimes I think what I did wrong was impls that look like Deserialize<'de> for &'de T. You can see it in this commit. Those are fixed now.

My concern in this issue is resolved by the recommendation I give in that lifetimes page:

- // Do not do this. Sooner or later you will be sad.
- impl<'de> Deserialize<'de> for Q<'de> {

+ // Do this instead.
+ impl<'de: 'a, 'a> Deserialize<'de> for Q<'a> {

There is still one other aspect I continue to be concerned about that I raised on the internals forum.