Store unboxed closure in struct

How to store an unboxed closure in struct?

pub struct Reader<R, T, F: FnOnce(R) -> T> {
    f: F,
}

The compiler complains:

error[E0392]: parameter `R` is never used
 --> src/lib.rs:1:19
  |
1 | pub struct Reader<R, T, F: FnOnce(R) -> T> {
  |                   ^ unused parameter
  |
  = help: consider removing `R`, referring to it in a field, or using a marker such as `PhantomData`
  = help: if you intended `R` to be a const parameter, use `const R: usize` instead

But using a marker such as PhantomData doesn't make sense, since the struct doesn't own R and T.

Also I don't want to use Box to avoid heap allocation.

add PhantomData<fn(R) -> T>

1 Like

No, please don't.

Instead, remove the superfluous trait bound from the type:

pub struct Reader<F> {
    f: F,
}

Only add back the parameters R and T on any impl block that actually requires them. You don't need the FnOnce bound for merely creating an instance of the struct, so you shouldn't add such an artificial restriction.

Incidentally, this is not a good reason to add PhantomData. Adding PhantomData<R> will mean that the compiler will behave as if the struct contained an R, which will affect variance and the implementation of auto traits such as Send and Sync. This will potentially lead to surprising errors due to an over-constrained implementation.

If anything, a PhantomData<fn() -> R> could be used, but here, even that isn't warranted.

4 Likes

One could argue you only need the bound at impl block, but adding R and T at type level has actual usage. Currently, a single type X could have multiple FnOnce implementation, without R and T at type level, you could run into multiple issue if you are implementing something over T.

I mean I did write PhantomData<fn(R) -> T> which has the correct variance. And your proposed PhantomData<fn() -> R> got the variance reversed.

Simply moving the constraint to impl block will result in the unconstrained type parameter error:

pub struct Reader<F> {
    f: F,
}

impl<R, T, F: FnOnce(R) -> T> Reader<F> {
    fn f() {}
}
error[E0207]: the type parameter `R` is not constrained by the impl trait, self type, or predicates
 --> src/main.rs:5:6
  |
5 | impl<R, T, F: FnOnce(R) -> T> Reader<F> {
  |      ^ unconstrained type parameter

Of course; the fact that the type parameters are unused isn't changed by where they are. The correct spelling would be

impl<F> Reader<F> {
    fn f<R, T>()
    where
        F: FnOnce(R) -> T,
    {}
}
1 Like

Sorry, I missed the exact type of the PhantomData. However, PhantomData<fn(R) -> T> is technically still too restrictive, because it determines variance even when F is not an Fn* type.

1 Like