Clinton
Clinton

Reputation: 23135

Lifetimes in generic trait implementation

I want to make a trait similar to the below structure (my particular use case is a bit more complex but this captures the issue and error I'm getting). The issue I have is which lifetimes in the last impl. I think I need to squeeze them into the trait definition but I'm not sure how. How can I sort out the lifetimes so this compiles?

Rust playground link to code

trait MyTrait<TIn> {
    fn f<TOut, F>(f: F, x: Self) -> TOut
    where
        F: Fn(TIn) -> TOut;
}

impl<T> MyTrait<T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(T) -> TOut,
    {
        f(x)
    }
}

impl<T> MyTrait<T> for &T
where
    T: Clone,
{
    fn f<TOut, F>(f: F, x: &T) -> TOut
    where
        F: Fn(T) -> TOut,
    {
        f(x.clone())
    }
}

// This impl fails to compile:
impl<T> MyTrait<&T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&T) -> TOut,
    {
        f(&x)
    }
}

Upvotes: 2

Views: 187

Answers (2)

HesLg
HesLg

Reputation: 48

The type signature

impl<T> MyTrait<&T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&T) -> TOut,
    {
    }
}

desugars to

impl<'a, T: 'a> MyTrait<&'a T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: for<'r> Fn(&'r T) -> TOut,
    {
    }
}

which is more general than the trait's type signature. Using

impl<'a, T: 'a> MyTrait<&'a T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&'a T) -> TOut,
    {
    }
}

would allow this to compile but restrict the implementation to either non-termination or unsafe code.

impl<'a, T: 'a> MyTrait<&'a T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&'a T) -> TOut,
    {
        //panic!(); or
        f(unsafe { &*(&x as *const T) })
    }
}

The unsafe version can easily cause a use after free, e.g.

println!("{:?}", String::f(|x: &String| x, "aa".to_string()));

You can instead move the bounds on F up (Playground)

trait MyTrait<TIn, F, TOut>
where
    F: Fn(TIn) -> TOut,
{
    fn f(f: F, x: Self) -> TOut;
}

impl<T, F, TOut> MyTrait<T, F, TOut> for T
where
    F: Fn(T) -> TOut,
{
    fn f(f: F, x: T) -> TOut {
        f(x)
    }
}

impl<T, F, TOut> MyTrait<T, F, TOut> for &T
where
    T: Clone,
    F: Fn(T) -> TOut,
{
    fn f(f: F, x: &T) -> TOut {
        f(x.clone())
    }
}

impl<T, F, TOut> MyTrait<&T, F, TOut> for T
where
    F: Fn(&T) -> TOut,
{
    fn f(f: F, x: T) -> TOut {
        f(&x)
    }
}

Upvotes: 1

rodrigo
rodrigo

Reputation: 98348

I think that your last trait does not compile because it is inherently unsafe.

Your impl is actually equivalent to:

impl<'a, T> MyTrait<&'a T> for T

That means, that for any type T and any lifetime 'a, T implements MyTrait<&'a T>. In particular, if 'a is for example 'static, then T implements MyTrait<&'static T>. So I could write something like this:

fn foo(x: &'static i32) -> &'static i32{
     x
}

fn main() {
    let sp: &'static i32 = {
        <i32 as MyTrait<&'static i32>>::f(foo, 42)
    };
    *sp = 0; //crash!
}

(I'm not sure, but I think you do not even need the ' static here to make it crash. I cannot test it, because it does not compile!).

This case is forbidden by the type system because the trait requires:

F: Fn(TIn) -> TOut;

but when TIn is a &T, it is actually:

F: for <'r> Fn(&'r TIn) -> TOut;

that is strictly more generic than the trait.

The only way I see you can write this safely is with something like this:

impl<T: 'static> MyTrait<&'static T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&'static T) -> TOut,
    {
        f(...)
    }
}

But this is probably not what you want, because you cannot use x as argument. Note that you even need to make T: 'static, to make it totally safe.

Upvotes: 0

Related Questions