3

This code doesn't compile (playground):

struct Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    mapper: F,
    // _t: PhantomData<T>,
    // _u: PhantomData<U>,
}

impl<T, U, F> Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    fn new(mapper: F) -> Self {
        Self {
            mapper,
            // _t: PhantomData,
            // _u: PhantomData,
        }
    }

    fn call(&self, t: T) -> U {
        (self.mapper)(t)
    }
}

The compiler says

error[E0392]: parameter `T` is never used
 --> src/main.rs:3:15
  |
3 | struct Mapper<T, U, F>
  |               ^ unused parameter
  |
  = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
  = help: if you intended `T` to be a const parameter, use `const T: usize` instead

error[E0392]: parameter `U` is never used
 --> src/main.rs:3:18
  |
3 | struct Mapper<T, U, F>
  |                  ^ unused parameter
  |
  = help: consider removing `U`, referring to it in a field, or using a marker such as `PhantomData`
  = help: if you intended `U` to be a const parameter, use `const U: usize` instead

However, this code compiles though I add PhantomData only for T:

use std::marker::PhantomData;

struct Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    mapper: F,
    _t: PhantomData<T>,
    // _u: PhantomData<U>,
}

impl<T, U, F> Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    fn new(mapper: F) -> Self {
        Self {
            mapper,
            _t: PhantomData,
            // _u: PhantomData,
        }
    }

    fn call(&self, t: T) -> U {
        (self.mapper)(t)
    }
}

On the other hand, if I add PhantomData only for U, the code doesn't compile:

use std::marker::PhantomData;

struct Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    mapper: F,
    // _t: PhantomData<T>,
    _u: PhantomData<U>,
}

impl<T, U, F> Mapper<T, U, F>
where
    F: Fn(T) -> U,
{
    fn new(mapper: F) -> Self {
        Self {
            mapper,
            // _t: PhantomData,
            _u: PhantomData,
        }
    }

    fn call(&self, t: T) -> U {
        (self.mapper)(t)
    }
}

fn main() {
    let mapper = Mapper::new(|a: usize| -> isize { -(a as isize) });
    println!("{}", mapper.call(3));
}

Why?

user3840170
  • 26,597
  • 4
  • 30
  • 62
ynn
  • 3,386
  • 2
  • 19
  • 42

1 Answers1

3

I believe the error is incorrect. This makes more sense with a regular trait with generics and associated types.

This compiles.

use std::ops::Add;

pub struct A<X, Z>
where
    X: Add<Output = Z>,
{
    pub adder: X,
}

This does not.

pub struct A<X, Y>
where
    X: Add<Y>,
{
    pub adder: X,
}

This gives two errors, even though Y is the only actual error.

pub struct A<X, Y, Z>
where
    X: Add<Y, Output = Z>,
{
    pub adder: X,
}

And if you look at the definition of Fn, you'll see that the parameter is a generic, while the return is an associated type (on FnOnce). So associated types count as usage, while generics do not.

Note that this is not an issue for most code, since you shouldn't put trait bounds on structs except in specific cases. You would simply use unbound generics for any generic fields, and then bind them to traits in the implementation. This compiles fine.

pub struct A<X> {
    pub adder: X,
}

impl<X> A<X>
{
    pub fn new<Y>(x: X, y: Y) -> Self 
    where
        X: Add<Y, Output = X>,
    {
        Self {
            adder: x + y,
        }
    }
}

There may still be need for PhantomData, especially if you need to implement a certain trait, but in most cases you can rearrange your generics to avoid it.

drewtato
  • 6,783
  • 1
  • 12
  • 17
  • Following your advice, I tried to refactor my code to avoid `PhantomData`. Here's the [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f3818fb57157dd6741f34e773395e2e4), but this results in another kind of redundancy (repetition of `where` clause). Do you have any idea to write this in a more elegant way? – ynn Aug 05 '23 at 07:04
  • @ynn you don't need `new` to be generic at all. Just make it `fn new(mapper: F) -> Self` – drewtato Aug 05 '23 at 07:32
  • It works ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bd9c94a4d7120925e175595b2cd8928f)). Thank you. – ynn Aug 05 '23 at 07:46