为什么一些仅在特质限制中使用类型参数的代码可以编译而无需PhantomData?

huangapple go评论85阅读模式
英文:

Why does some code with type parameters used only in trait bounds compile without PhantomData?

问题

这段代码无法编译,编译器报告以下错误:

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

然而,当你只为 T 添加 PhantomData 时,代码可以编译:

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

然而,如果你只为 U 添加 PhantomData,代码将无法编译。这是因为编译器会检查结构体字段是否被使用,如果没有字段使用参数 U,编译器将报告错误。在这种情况下,没有任何字段使用 U,所以编译器认为 U 是未使用的。

如果你打算保留 U 参数但不在结构体中使用它,你可以使用注释或其他方式来引用它,以避免编译错误。或者你可以考虑使用 PhantomDataU 创建一个字段,就像你在第一个示例中为 T 所做的那样。这将告诉编译器 U 参数是有意义的,并且不会被删除。

英文:

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?

答案1

得分: 3

我认为错误是不正确的。将泛型和关联类型与常规特性更相符。

这个编译。

use std::ops::Add;

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

这个不行。

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

这会出现两个错误,尽管 Y 是唯一的实际错误。

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

如果你查看 Fn 的定义,你会看到参数是一个泛型,而返回值是一个关联类型(在 FnOnce 上)。所以关联类型算作使用,而泛型则不算。

请注意,对于大多数代码来说,这不是问题,因为你 不应该在结构体上放置特性约束,除非在特定情况下。你只需对任何泛型字段使用无界泛型,然后在实现中将它们绑定到特性。这可以很好地编译。

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

可能仍然需要 PhantomData,特别是如果你需要实现某个特性,但在大多数情况下,你可以重新排列你的泛型以避免它。

英文:

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&lt;X, Z&gt;
where
    X: Add&lt;Output = Z&gt;,
{
    pub adder: X,
}

This does not.

pub struct A&lt;X, Y&gt;
where
    X: Add&lt;Y&gt;,
{
    pub adder: X,
}

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

pub struct A&lt;X, Y, Z&gt;
where
    X: Add&lt;Y, Output = Z&gt;,
{
    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&lt;X&gt; {
    pub adder: X,
}

impl&lt;X&gt; A&lt;X&gt;
{
    pub fn new&lt;Y&gt;(x: X, y: Y) -&gt; Self 
    where
        X: Add&lt;Y, Output = X&gt;,
    {
        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.

huangapple
  • 本文由 发表于 2023年8月5日 13:45:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76840309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定