Fn pointer of 'constructor' with explicit lifetimes

In order to get the lifetimes right I already minified my problem:

struct A {
    data: [u8; 40]
}

struct B<'a> {
    borrow: &'a A
}

trait C<'b> {
    fn new_c_implementor(borrow: &'b A) -> Box<dyn C + 'b> where Self: Sized;
}

impl<'a> C<'a> for B<'a> {
    fn new_c_implementor(borrow: &'a A) -> Box<dyn C + 'a> where Self: Sized {
        Box::new(Self {
            borrow
        })
    }
}

Now I need to write a function get_correct_c_implementor, that returns a fn pointer to the new_c_implementor 'constructor'.

However I've ran into a bit of trouble with defining the types of the fn pointer, because in a standalone function I obviously can't have lifetimes in the impl block

fn correct_c_implementor() -> fn(&A) -> Box<dyn C> {
    B::new_c_implementor
}

doesn't work (which I mostly expected). I'm getting an error message, that

error[E0308]: mismatched types
  --> src/main.rs:23:5
   |
22 | fn correct_c_implementor() -> fn(&A) -> Box<dyn C> {
   |                               -------------------- expected `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)>` because of return type
23 |     B::new_c_implementor
   |     ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)>`
                 found fn item `fn(&A) -> Box<dyn C<'_>> {<B<'_> as C<'_>>::new_c_implementor}`

This says expected for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)>` Now I know that this is probably not the type I want because of the 'static trait bound but I still tried simply copy-pasting the type the compiler expected into my code.

For some reason I'm still getting the same error

error[E0308]: mismatched types
  --> src/main.rs:23:5
   |
22 | fn correct_c_implementor() -> for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)> {
   |                               ----------------------------------------------- expected `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)>` because of return type
23 |     B::new_c_implementor
   |     ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'static)>`
                 found fn item `fn(&A) -> Box<dyn C<'_>> {<B<'_> as C<'_>>::new_c_implementor}`

I assume that 'a != 'a but I'm unsure how I should add the generic lifetime to B<'a> to make 'a == 'a (because there's no impl block)

I expected the lifetime to be for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'a)> but that doesn't work either.


error[E0308]: mismatched types
  --> src/main.rs:23:5
   |
22 | fn correct_c_implementor() -> for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'a)> {
   |                               ------------------------------------------ expected `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'a)>` because of return type
23 |     B::new_c_implementor
   |     ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'a> fn(&'a A) -> Box<(dyn C<'a> + 'a)>`
                 found fn item `fn(&A) -> Box<dyn C<'_>> {<B<'_> as C<'_>>::new_c_implementor}`

With the error again implying, that 'a != 'a?

+The error message is really confusing. Should I make a bug report about this?

Box<dyn C> is always Box<dyn 'static + C>, even though C contains a lifetime parameter... Lifetime elision - The Rust Reference

For OP, the simplest solution is Rust Playground

fn correct_c_implementor<'a>() -> fn(&'a A) -> Box<dyn 'a + C> { // compiles
    B/*::<'a>*/::new_c_implementor
}

fn check() { // compiles
    let new = correct_c_implementor();
    new(&A { data: [0; 40] });
}

Well, it's not HRTB anymore. If this works for you, it's good.

A new design is split new_c_implementor from trait C: Rust Playground

trait C {}

impl C for B<'_> {}

impl B<'_> {
    fn new_c_implementor<'a>(borrow: &'a A) -> Box<dyn 'a + C> {
        Box::new(B { borrow })
    }
}

fn correct_c_implementor() -> for<'a> fn(&'a A) -> Box<dyn 'a + C> {
    B::new_c_implementor
}

Or if you want new_c_implementor to be kind of generic. Rust Playground

trait C {}

impl C for B<'_> {}

trait CImplementor {
    fn new_c_implementor<'a>(borrow: &'a A) -> Box<dyn 'a + C>;
}
impl CImplementor for B<'_> {
    fn new_c_implementor<'a>(borrow: &'a A) -> Box<dyn 'a + C> {
        Box::new(B { borrow })
    }
}

fn new_c_implementor() -> for<'a> fn(&'a A) -> Box<dyn 'a + C> {
    B::new_c_implementor
}

fn need_c_implementor<CI>() -> impl for<'a> Fn(&'a A) -> Box<dyn 'a + C>
where
    CI: CImplementor,
{
    CI::new_c_implementor
}

I'd say this is the most direct fix.

//                                                    vvvv
fn correct_c_implementor() -> fn(&A) -> Box<dyn C<'_> + '_> {
    |a| B::new_c_implementor(a)
//  ^^^                     ^^^
}

// Akin to
fn closure<'a>(a: &'a A) -> Box<dyn C<'a> + 'a> {
    <B<'a> as C<'a>>::new_c_implementor(a)
}

The Box<dyn C<'_> + 'static> form can't be met. After correcting that, the only remaining problem is that <B<'a> as C<'a>>::new_c_implementor doesn't have a higher-ranked Fn implementation, because 'a is already "fixed" within the trait definition. The closure allows you to "move the binder" to where you need it.

1 Like

I actually came up with a different solution from the 2 answers, which let me remove the generic lifetime on the Trait, so I just went with that.

impl C for B<'_> {
    fn new_c_implementor(borrow: &A) -> Box<dyn C + '_> where Self: Sized {
        Box::new(B {
            borrow
        })
    }
}

This made it a lot more clear why elision wasn't working as expected.

Anyways considering the elison rules we can omit the 'a in the fn signature from vague's solution

fn correct_c_implementor() -> fn(&A) -> Box<dyn C + '_> { // compiles
    B/*::<'a>*/::new_c_implementor
}

(I assume you knew this and just kept the lifetimes expanded to make it more clear?) Well anyways that is also a pretty good solution

That sadly doesn't work for me but would probably be the easiest fix otherwise

I didn't really get that? Could you elaborate?

I think the solution by moving an early bound to late bound via closures deserves a compiler hint fix as the compiler suggests in this case.

1 Like

That's what I wrote by trait CImplementor in the first reply, but the lifetime parameters are just omitted in yours. Rust Playground

Here's an example with comments. I hope it helps; feel free to ask more questions.

Edit: I also walk through the same phenomenon in this topic.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.