4
votes

I have a struct that contains a reference and so it has a lifetime parameter. I'd like to pass around the function pointer of a method of this struct. Later, I will call that function with an instance of the struct. I ran into snags while trying to store the function pointer, eventually finding this solution:

struct Alpha<'a> { a: &'a u8 }

impl<'a> Alpha<'a> {
    fn alpha(&self) -> u8 { *self.a }
}

struct Try1(fn(&Alpha) -> u8);
struct Try2(for<'z> fn(&Alpha<'z>) -> u8);
struct Try3<'z>(fn(&Alpha<'z>) -> u8);

fn main() {
    Try1(Alpha::alpha); // Nope
    Try2(Alpha::alpha); // Nope
    Try3(Alpha::alpha);
}

Unfortunately, this solution doesn't work for my real case because I want to implement a trait that has its own notion of lifetimes:

trait Zippy {
    fn greet<'a>(&self, &Alpha<'a>);
}

impl<'z> Zippy for Try3<'z> {
    fn greet<'a>(&self, a: &Alpha<'a>) { println!("Hello, {}", self.0(a)) }
}

Produces the error:

error: mismatched types:
 expected `&Alpha<'z>`,
    found `&Alpha<'a>`

I feel that I shouldn't need to tie the lifetime of my struct Try3 to the lifetime of the parameter of the function pointer, but the compiler must be seeing something I'm not.

1
As a work around, you can either define a function fn hack(a: &Alpha) -> u8 { Alpha::alpha(a) } or use mem::transmute to get a suitable value for Try1 or Try2.wingedsubmariner
@wingedsubmariner If you add the first workaround as an answer, I'd be happy to accept it.Shepmaster

1 Answers

1
votes

Unfortunately, the function alpha implemented on the struct Alpha effectively takes the struct's lifetime as a parameter, despite not actually using it. This is a limitation of the syntax for defining methods on structs with lifetimes. So even though it is possible to take a pointer to it as a for<'z> fn(&Alpha<'z>) -> u8, it is not possible to treat it as a fn(&Alpha) -> u8, even though the definition suggests this should be possible.

This can be worked around by defining a function that invokes the method and take a pointer to it instead:

fn workaround(a: &Alpha) -> u8 { Alpha::alpha(a) }
Try1(workaround);

In fact, it may be better to do it the other way around, with the definition in the function and the method invoking the function. Then when the function is invoked through a fn(&Alpha) -> u8 pointer a second jump won't be necessary into the method, and calls to the method can be inlined as calls to the function.