How to handle reference and value types under one trait?

I've just started off with Rust a month ago, and I'm finding it a fascinating language, but I've got a bit stuck on this:

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. Note all the impls themselves will compile as independent functions which I believe means with enough genericity I should be able to place them under one trait. How can I sort out the lifetimes (for both the trait and the impls) so this compiles?

Rust playground link to code

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

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

impl<T> MyTrait<T> for &T
where
    T: Clone,
{
    fn g<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 g<TOut, F>(f: F, x: T) -> TOut
    where
        F: Fn(&T) -> TOut,
    {
        f(&x)
    }
}

(Note, this is a cross post from StackOverflow)

(Edit: renamed function names from f to g so they don't clash with parameters and cause confusion)

You can't make that last impl because you would need to be able to name the local scope of the function to describe the borrow of x, but that isn't possible.

But it seems like I can write the function:

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

so I'm trying to understand why I can't make it a trait function

Yes, but that is using some sugar. The desugaring is

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

This function has a incompatible with the function in the trait.
for<'a> means for all lifetimes 'a , this bound applies.

The trait function should be

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

Note the differences in the lifetimes. This is what causes the new lifetime error.

Can't we just put the lifetimes as trait parameters also then? Or can this function just not be a trait function for some reason?

You can make this trait function, just as I showed, but you won't be able to the closure f. Note that this compiles: playground

I don't really understand what you're getting at with that playground link.

I can compile the function as a non-trait function, with an actual, non-"unimplemented" implementation.

Are you saying despite that, there's some functions that I can't put inside a trait (that is, aside from making them have an undefined result), and this is one of them?

I am saying that there is nothing wrong with the definition of the trait functions, but there is no way to safely implement the problematic one.

I'll give an example of how it could be badly used.

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

// pretending that this compiles
impl<'a, T: 'a> MyTrait<&'a T> for T {
    fn f<TOut, F>(f: F, x: T) -> TOut
    where
        F: FnOnce(&'a T) -> TOut,
    {
        f(&x)
    }
}

fn main() {
    let mut reference: &'static &'static str = &"hi";
    
    <&'static str as MyTrait<&'static &'static str>>::f(
        |x: &'static &'static str| {
            reference = x;
        },
        "Hello World!"
    );

    println!("{}", reference); // this would be a dangling pointer if the trait impl above compiled.
}

I put a playground example that uses unsafe to show a seg-fault if your program compiled.

Is the problematic one unsafe when it's not part of a trait?

i.e. is

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

unsafe?

If so, why have I got away with making it without using "unsafe"? If not, why can't I make it a trait function?

no the unsafe bit is f(&x) nothing else. The lifetime of the reference is shorter than 'a so you can't pass it in to f. So Rust rightly rejects it, and you haven't gotten away with it :slight_smile: .

Also, the function you are showing now, and the one in the trait are different.

Rust is accepting the code in my previous post. It has the f(&x) bit and Rust thinks it's fine.

Yes, but that function is different from the function in the trait,

i.e. now

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

is not the same as

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

The second one is the one from the trait, and that doesn't compile.


Note that the first one is the same as

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

Cool can I make the first one part of the trait then?

No, your trait declaration forbids it. If you want, you can change your trait declaration to

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

And avoid the problem entirely, but this has different downsides

I'm happy to change the trait definition as I said in the OP just as long as I can define those three functions underneath it.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.