Reputation: 23135
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?
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
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
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