Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
854 views
in Technique[技术] by (71.8m points)

rust - Unused type parameter on closure argument

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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>>
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...