How to implement trait for function pointers handling lifetimes?

I have the following code from a larger project.

trait Wanted {
    fn wanted() -> bool;
}

struct S<'a, T> {
    x: &'a T,
}

impl Wanted for u8 {
    fn wanted() -> bool {
        true
    }
}

impl Wanted for u16 {
    fn wanted() -> bool {
        true
    }
}

type A = extern "C" fn(x: S<u8>);
type B = extern "C" fn(x: S<u16>);

// How to change **only this impl** to make both `a` and `b` work at the same time?
impl<'a, T> Wanted for extern "C" fn(x: S<'a, T>)
where
    T: Wanted,
{
    fn wanted() -> bool {
        T::wanted()
    }
}

fn main() {
    let a = A::wanted();
    let b = B::wanted();
}

(Link to playground)

Due to the way I use proc macros I want to generically implement a trait Wanted for classes of function pointers if those function pointers use parameters which also implement that trait.

I also happen to have type aliases A and B used elsewhere.

I now want to generate a function that, similar to main, can give meta information whether A or B has the wanted property.

The only code I would like to change right now is (due to architectural reasons) is the impl<'a, T> Wanted for ... section. In particular I would not like having to write explicit impl ... for A.

Questions:

  • How can I make main() compile?

  • Also, could someone ELI5 why the existing impl doesn't work? Naively I'd assume impl<'a, T> means that for any lifetime 'a and type T trait Wanted will be implemented as given. Therefore, when using type A which (if I understand correctly) has a hidden for<'a> that should still be qualified by the impl block. Clearly I'm misunderstanding something here, but it would be nice to understand what.

Thanks!

1 Like

Well, to make your code compile,

// How to change **only this impl** to make both `a` and `b` work at the same time?
- impl<'a, T> Wanted for extern "C" fn(x: S<'_, T>)
+ impl<T> Wanted for extern "C" fn(x: S<T>)
where
    T: Wanted,
{
    fn wanted() -> bool {
        T::wanted()
    }
}

As to explanation: There are two different function pointer types at play here. Let’s take T==u8.
The types

extern "C" fn(x: S<'a, u8>)
// and
extern "C" fn(x: S<'_, u8>)

are different due to a syntactical convention for fn pointers and Fn traits with elided lifetimes. The type

extern "C" fn(x: S<'_, u8>)

actually desugars as a higher-ranked function pointer type

for<'a> extern "C" fn(x: S<'a, u8>)

Note that S<u8> is equivalent to S<'_, u8> (although I would, for clarity, usually suggest using S<'_, u8> because that’s not hiding the fact that there’s a lifetime parameter. So these three types are the same

extern "C" fn(x: S<'_, u8>)
extern "C" fn(x: S<u8>)
for<'a> extern "C" fn(x: S<'a, u8>)

(and you could use any of them in the impl)

This function pointer type is a function pointer that’s generic over 'a and can be applied to arguments of type S<'a, u8> for any lifetime 'a, OTOT, the type

extern "C" fn(x: S<'a, u8>)

is non-generic / not higher order / can only be applied to S<'a, u8> for a single fixed concrete lifetime 'a. In practice you can coerce a

for<'a> extern "C" fn(x: S<'a, u8>)

into a

extern "C" fn(x: S<'b, u8>)

of any concrete lifetime, so you can use the higher-order variant and pass it to functions expecting the non-generic one, etc… but they are different types and trait resolution does not like to use your original trait impl, which is a generic impl for every version of the non-generic function pointer type on the generic function pointer type.

It depends on your use cases what you actually want. Are there also instances where you’ll provide a non-generic function pointers as the type? In this case you might even want both impls

impl<T> Wanted for extern "C" fn(x: S<T>)
where
    T: Wanted,
{
    fn wanted() -> bool {
        T::wanted()
    }
}
impl<'a, T> Wanted for extern "C" fn(x: S<'a, T>)
where
    T: Wanted,
{
    fn wanted() -> bool {
        T::wanted()
    }
}

although then rustc becomes partially unhappy and talks about (only a warning)

warning: conflicting implementations of trait `Wanted` for type `for<'r> extern "C" fn(S<'r, _>)`
  --> src/main.rs:32:1
   |
24 | / impl<T> Wanted for extern "C" fn(x: S<T>)
25 | | where
26 | |     T: Wanted,
27 | | {
...  |
30 | |     }
31 | | }
   | |_- first implementation here
32 | / impl<'a, T> Wanted for extern "C" fn(x: S<'a, T>)
33 | | where
34 | |     T: Wanted,
35 | | {
...  |
38 | |     }
39 | | }
   | |_^ conflicting implementation for `for<'r> extern "C" fn(S<'r, _>)`
   |
   = note: `#[warn(coherence_leak_check)]` on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #56105 <https://github.com/rust-lang/rust/issues/56105>
   = note: this behavior recently changed as a result of a bug fix; see rust-lang/rust#56105 for details

which is going to quickly get you a level of subtlety and not-sure-if-this-is-okay-what-I’m-doing-here-anymore that you might not want to deal with.

Anyways, hope this helped (^_^)

2 Likes

Wow, thanks! I never realized for<'a> is actually part of the type and not only 'quantifying' it. I will need a bit to digest your answer and apply it to my code.

I now fear my post might have been a bit oversimplified, since I internally use a T: Wanted for the function parameter, but need to check what happens.

An immediate question though, you wrote

extern "C" fn(x: S<'a, u8>) is non-generic / not higher order / can only be applied to S<'a, u8> for a single fixed concrete lifetime 'a

I have some trouble understanding why the impl<'a, T> does not, however, produce the same effect, in terms of if there is any situation where the trait implementation wouldn't hold (I guess my question is more about the 'true' meaning of impl<...>):

For what I understand impl<T> Trait for S<T> {} means that for any T (well, Sized anyway) a given trait is implemented as given.

When I now write impl<T> Wanted for extern "C" fn(x: S<T>) you say this is equivalent to impl<T> Wanted for for<'x> extern "C" fn(x: S<'x, T>).

I think I get that part, but what I don't understand is why, when I have impl<'a, T> Wanted for extern "C" fn(x: S<'a, T>) and then later use a for<'x> extern "C" fn(x: S<'x, T>), Rust would not be able to see that for whatever x it picked there already is an implementation using <'a> produced by the impl<'a, T>.

I suppose my question boils down to:

  • Practically, what 'x could there ever be which is not covered by the 'a version.

  • And if that held practically, why are these concepts then still distinguished formally (isn't it really just about ensuring that for any extern "C" fn(x: S<'a, u8>) the trait is implemented)?

    ... ok, I think now I'm really just questioning why for<'a> ... is another type than ..., since I assume the quantifier is, in the grand scheme of things, actually useless and only exists to ensure some other used lifetime fulfill a certain property).

Well, to explain why the syntax uses a quantifier, it’s based on HRTB (higher-ranked trait bounds).

Writing T: for<'a> Trait<'a> really is the same as having T: Trait<'a> for all lifetimes 'a.

Then the next step is to look at trait objects:

You can have Box<dyn Trait<'a> or Box<dyn for<'a> Trait<'a>>, and these are obviously different types. Somewhat similar to how Box<dyn OtherTrait> and Box<dyn OtherTrait + Send> are different. Sure, you can turn a Box<dyn OtherTrait + Send> into a Box<dyn OtherTrait>, (and by the way not the other way around), but if you implement some trait Foo for Box<dyn OtherTrait>, you can’t use it on a ``Box<dyn OtherTrait + Send>` since it’s just not the same type.

Similarly, you can turn Box<dyn for<'a> Trait<'a>> into Box<dyn Trait<'concrete_lifetime> (and not the other way), but they’re still distingt types. It’s a bit like you can turn &'static Foo into &'a Foo or &Vec<T> into &[T], they’re different types, just coercible.

Now, trait objects for Fn* traits like Box<dyn for<'a> Fn(S<'a, T>)> or Box<dyn Fn(S<'concrete_lifetime, T>)> are actually, conceptually, quite similar in lots of ways to fn pointer types for<'a> fn(S<'a, T>) or fn(S<'concrete_lifetime, T>), and this similarity also explains why they use the same syntax. In particular both dyn Fn and fn pointer support the elision to write Box<dyn for<'a> Fn(S<'a, T>)> as Box<dyn Fn(S<'_, T>)> and for<'a> fn(S<'a, T>) as fn(S<'_, T>)

2 Likes

As I feared, my code was a bit different, which now gave me trouble using the for for<'a> pattern.

Right now I have:

trait Wanted {
    fn wanted() -> bool;
}

struct S<'a, T> {
    x: &'a T,
}

impl Wanted for u8 {
    fn wanted() -> bool {
        true
    }
}

impl Wanted for u16 {
    fn wanted() -> bool {
        true
    }
}

impl<'a, T> Wanted for S<'a, T> {
    fn wanted() -> bool {
        true
    }
}

type A = extern "C" fn(x: S<u8>);
type B = extern "C" fn(x: S<u16>);

// How to change **only this impl** to make both `a` and `b` work at the same time?
impl<T> Wanted for extern "C" fn(x: T)
where
    T: Wanted,
{
    fn wanted() -> bool {
        T::wanted()
    }
}

fn main() {
    let a = <A as Wanted>::wanted();
    let b = <B as Wanted>::wanted();
}

(Playground)

which gives:

2  |     fn wanted() -> bool;
   |     -------------------- due to a where-clause on `Wanted::wanted`...
...
41 |     let a = <A as Wanted>::wanted();
   |             ^^^^^^^^^^^^^^^^^^^^^ doesn't satisfy where-clause
   |
   = note: ...`Wanted` would have to be implemented for the type `for<'r> extern "C" fn(S<'r, u8>)`
   = note: ...but `Wanted` is actually implemented for the type `extern "C" fn(S<'0, u8>)`, for some specific lifetime `'0`

But when trying

impl<T> Wanted for for<'a> extern "C" fn(x: T)
where
    T: Wanted,

I now can't connect 'a with T and still get the same error (as it apparently doesn't understand the 'a shall apply to whatever is in T (and I can't use T: 'a in where).

Ahh… well. That’s a bummer, I don’t think there is a way here. Maybe you can let type inference help you… let’s see… something like this works for ordinary fn pointers:

// HowToGetThere encodes some nondeterministic choices, e.g. from
// for<'a> fn(&'a T) to the choice of concrete `&'a T`, etc…

// Without a choice, this is (),
// otherwise this recursively builds a list using tuple-types
// (Choice1, (Choice2, (Choice3, ()))
// etc.

// Intended to be filled in by type inference when calling `T::wanted()`.

trait Wanted<HowToGetThere = ()> {
    fn wanted() -> bool;
}

struct S<'a, T> {
    x: &'a T,
}

impl Wanted for u8 {
    fn wanted() -> bool {
        true
    }
}

impl Wanted for u16 {
    fn wanted() -> bool {
        true
    }
}

impl<'a, T> Wanted for S<'a, T> {
    fn wanted() -> bool {
        true
    }
}

type A = fn(x: S<u8>);
type B = fn(x: S<u16>);

// Fns offer some choice of argument type, so HowToGetThere is used
impl<F: FnOnce(T), T: Wanted<H>, H> Wanted<(T, H)> for F
{
    fn wanted() -> bool {
        T::wanted()
    }
}

fn main() {
    let a = A::wanted();
    let b = B::wanted();
}

Seems like extern "C" fn pointers don’t implement FnOnce… what a disappointment. There’s really no existing trait that lets us “look into” the argument (or return) types of higher-ranked extern "C" fn pointer types and no way to generically implement something for all higher-ranked extern "C" fn pointer types (even just for a fixed number of arguments) either AFAICT.

2 Likes

Ah, I wasn't really away how creatively for<'a> types can be used / impact safety (well, ok, up until a few minutes ago I wasn't even aware they were a thing...)

Ahh… well. That’s a bummer, I don’t think there is a way here. Maybe you can let type inference help you… let’s see… something like this works for ordinary fn pointers:

Oh well. I think I'll have to rework a few thing then (we have to use extern "C" pointers unfortunately since we use it for FFI).

In any case, thank you so much again for the detailed explanations; I think you really helped me untangle a few bounds-related concepts I always got the wrong way!

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.