Unused type parameter on closure argument
As @VladimirMatveev says, the return type of a closure is an associated type.
An associated type is different from a type parameter because its value is determined when you implement a trait, not when you use it in a call.
In Fn(I) -> Option<T>
, once you have the input (of type I
) and the implementation (the particular operations defined in the closure you're passing), the Option<T>
output is determined.
For I
it's different, though. You need to either use the type in the struct, or to show the compiler how it would be theoretically used, with a PhantomData
field.
use std::marker::PhantomData;
struct Bar<I, T, F>
where
F: Fn(I) -> Option<T>,
{
f: F,
_marker: PhantomData<I>,
}
PhantomData
is only used to check types, but is erased in the generated code, so it does not occupy any memory in your struct (that's why it's a phantom).
The reason why it is needed is explained in detail in RFC 738 on variance. I'll try to give you a shorter (and hopefully correct) version here.
In Rust, you can most of the times (but not always!) use a longer lifetime where a shorter one is expected.
fn foo<'short, 'long>(_a: &'short i32, b: &'long i32)
where
'long: 'short,
{
let _shortened: &'short i32 = b; // we're binding b to a shorter lifetime
}
fn foo2<'short, 'long>(_a: &'short i32, b: &'long Cell<&'long i32>)
where
'long: 'short,
{
let _shortened: &Cell<&'short i32> = b;
}
(playground)
The RFC explains why Cell
expects exactly the same (and not a longer) lifetime, but for now I suggest you just trust the compiler that it would be unsafe to allow foo2
to compile.
Now pretend you have a
struct Foo<T> { t: T }
That T
can be anything, including a type that holds references.
In particular, T
can be a type like & i32
or a type like &Cell<&i32>
.
As with our foo
functions above, Rust can infer just fine when it can or can't allow us to assign to a shorter lifetime by inspecting the type of T
(playground).
However, when you have an unused type parameter, inference does not have any field to inspect to know how it should allow the type to behave with lifetimes.
If you have
struct Foo<T>; // unused type parameter!
Rust asks you to specify with a PhantomType
if you wish your T
to behave as if it was a & i32
or like a Cell
. You would write:
struct Foo<T> {
marker: PhantomData<T>, // this is what you usually want
// unless you're working with unsafe code and
// raw pointers
}
or you could write:
struct Foo<T> {
marker: PhantomData<Cell<T>>
}
An alternative to using dynamic dispatch via the trait object is to use std::marker::PhantomData
for this:
use std::marker::PhantomData;
struct Bar<I, T, F>
where
F: Fn(I) -> Option<T>,
{
f: F,
_i: PhantomData<I>,
_t: PhantomData<T>,
}
(playground)
You "instantiate" a PhantomData
just by using PhantomData
, e.g.
let phantom: PhantomData<T> = PhantomData;