This works:
struct Foo<T, F>
where
F: Fn() -> Option<T>,
{
f: F,
}
but this gives me compile errors:
struct Bar<I, T, F>
where
F: Fn(I) -> Option<T>,
{
f: F,
}
error[E0392]: parameter `I` is never used
--> src/lib.rs:1:12
|
1 | struct Bar<I, T, F>
| ^ unused parameter
|
= help: consider removing `I`, referring to it in a field, or using a marker such as `std::marker::PhantomData`
error[E0392]: parameter `T` is never used
--> src/lib.rs:1:15
|
1 | struct Bar<I, T, F>
| ^ unused parameter
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `std::marker::PhantomData`
Why is using a type parameter in the closure's return type ok, but not in its arguments?
I can get around it by storing the closure as a trait object:
struct Bar<I, T> {
f: Box<Fn(I) -> Option<T>>,
}
but I'd like to avoid this if possible.
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;
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>>
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With