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;

Tags:

Rust