Generic type unused for the compiler but needed

Hello,

I'm trying the compile the following struct:

struct Cacher<T,P,R>
    where T : Fn(P) -> R
{
    calculation : T,
    value : Option<R>
}

The compiler complains about 'P' and says 'unused type parameter'.
If i remove the 'P' the compiler complains again and says 'cannot find type P in this scope'

How can I keep 'P' and stop the compiler to complain?

Thank you,

1 Like

You can use a PhantomData.

struct Cacher<T, P, R>
where
    T: Fn(P) -> R,
{
    calculation: T,
    value: Option<R>,
    _marker: std::marker::PhantomData<fn(P)>,
}
2 Likes

Thank you,

Does it change the struct memory layout? (Edit: nevermind, 'Zero-sized type used to mark things' :slight_smile: )
Why the compiler is not able to see that P is used? It's very odd to add a member that is not intent to be used to stop the compiler to complains.

I'm not completely familiar with the specifics, so all this is just what I've come to understand, but it has to do with variance checking. If you had a T which implemented both Fn(&u32) -> R and Fn(&mut u32) -> R, which P would it pick? You have to have something in the struct to pin that down, so you add a marker member that says which P you've picked for that instance of the struct.

1 Like

Variance has to do with lifetimes, so a better example would be: if you have a function that takes Cacher<_, &'a u32, _> and you have an object that is Cacher<_, &'b u32, _>, can you pass object to function?

  • If Cacher is covariant in T, you can only pass object to function if 'b: 'a
  • If Cacher is contravariant in T, you can only pass it if 'a: 'b
  • If Cacher is invariant in T, you can only pass it if 'a: 'b and 'b: 'a

Traits are always invariant, but T can implement a whole family of Fn traits (e.g. for<'a> Fn(&'a u32)), so the trait bound itself doesn't give enough information to conclude that Cacher should be contravariant in T. A function is contravariant in its argument types, which is why PhantomData<fn(T)> is the correct choice here.

3 Likes

Thank you,

I found documentation in PhatomData page PhantomData in std::marker - Rust
It seems that the correct solution is to mark 'P' instead of 'Fn(P)'

struct Cacher<T,P,R>
    where T : Fn(P) -> R
{
    calculation : T,
    value : Option<R>,
    _marker : std::marker::PhantomData<P>
}

I think it should be contravariant. Your type does not actually contain P, but is rather a function of P.

5 Likes

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