Need help understanding a lifetime issue


#1

I’m having troubles understanding a compilation error.

Here is the code: (link to playpen)


trait BuildFromRef<'a> {
    fn build(&'a u32) -> Self;
}

struct Holder<'a>(&'a u32);
impl<'a> BuildFromRef<'a> for Holder<'a> {
    fn build(r: &'a u32) -> Holder<'a> {
        Holder(r)
    }
}


fn foo<T>(callback: fn(T)) where T: for<'s> BuildFromRef<'s> {
    let a = 5u32;
    
    let t = T::build(&a);
    callback(t);
}



fn callback<'a>(_: Holder<'a>) {}

fn main() {
    foo(callback);
}

And the error:

<anon>:26:5: 26:8 error: the trait `for<'s> BuildFromRef<'s>` is not implemented for the type `Holder<'_>` [E0277]
<anon>:26     foo(callback);
              ^~~

I’m trying to pass a callback which accepts a Holder (which is a dummy object). The function foo that accepts the callback can pass any type which can be built from a reference to a local variable. But this error occurs.


#2

A Holder<'x> implements BuildFrom<'x> only and nothing else. But you require a type T that satisfies T : for<'s> BuildFromRef<'s>. There is no such type T in your program because Holder<'a> and Holder<'b> are different types for 'a != 'b.

Here, I constrained the callback itselt, not its parameter type:

struct Holder<'a>(&'a u32);

fn foo<F>(callback: F) where F: for<'s> Fn(Holder<'s>) {
    let a = 5u32;
    let t = Holder(&a);
    callback(t);
}

fn callback<'a>(_: Holder<'a>) {}

fn main() {
    foo(callback);
}

Now, if you want to parameterize over the kind of type family (allow different Holder-like things other than Holder), this smells like a problem for HKTs (higher-kinded types) which Rust does not support yet.


#3

this smells like a problem for HKTs (higher-kinded types) which Rust does not support yet.

I guess I should just accept this as “you can’t do what you’re trying to do, period”?

:sigh:


#4

I only get this far:

fn foo<B, S>(builder: B, sink: S) where B: for<'a> Fn(&'a u32) -> ???,
                                        S: for<'a> Fn(???)
{
    let a = 5u32;
    let t = builder(&a);
    sink(t);
}

The problem is in specifying what ??? is. If you want to replace Holder with a generic parameter, it would have to be a parameter for a type constructor, not a type, because it somehow needs to be able to produce a type given a lifetime parameter. But there is no such thing (yet?). Rust only knows type and lifetime parameters.

Perhaps we will someday see something like this:

fn foo<B, T<'_>, S>(builder: B, sink: S) where B: for<'a> Fn(&'a u32) -> T<'a>,
                                               S: for<'a> Fn(T<'a>)

where T is a parameter for a type constructor taking a single lifetime.

Anyhow, I think you have to work around it somehow for now.


#5

I worked around it by specifying a precise lifetime and mem::transmuting the data once created so that I get the expected lifetime.
This is obviously very unsafe as the user can just request a Holder<'static> and store it somewhere, but there’s no other way.

The most problematic thing is that I’ll have to wait at least several months until this makes it into Rust stable (and that’s if I’m optimistic). It’s a bit disappointing when you were used to having new features added into Rust almost every week.