【问题标题】:Unused type parameter on closure argument闭包参数上未使用的类型参数
【发布时间】:2015-05-09 16:05:52
【问题描述】:

这行得通:

struct Foo<T, F>
where
    F: Fn() -> Option<T>,
{
    f: F,
}

但这给了我编译错误:

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`

为什么在闭包的返回类型中使用类型参数可以,但在其参数中却不行?

我可以通过将闭包存储为特征对象来解决它:

struct Bar<I, T> {
    f: Box<Fn(I) -> Option<T>>,
}

但如果可能的话,我想避免这种情况。

【问题讨论】:

  • 这个问题的长答案基本上是rfc 738 on variance
  • " 为什么在闭包的返回类型中使用类型参数可以,但在其参数中却不行?"我怀疑这与闭包返回类型与相应特征上的关联类型建模这一事实有某种联系,而参数类型是该特征的类型参数,但我不确定。
  • @VladimirMatveev 是的,这是在我链接的 rfc 末尾提到的,在 rules for associated types

标签: rust


【解决方案1】:

作为@VladimirMatveev says,闭包的返回类型是关联类型。

关联类型与类型参数不同,因为它的值是在您实现一个特征时确定的,而不是在您在调用中使用它时确定的。
Fn(I) -&gt; Option&lt;T&gt; 中,一旦你有了输入(I 类型)和实现(在你传递的闭包中定义的特定操作),Option&lt;T&gt; 输出就确定了。

但对于I,情况有所不同。您需要使用结构中的类型,或者通过PhantomData 字段向编译器显示理论上如何使用

use std::marker::PhantomData;

struct Bar<I, T, F>
where
    F: Fn(I) -> Option<T>,
{
    f: F,
    _marker: PhantomData<I>,
}

PhantomData 仅用于检查类型,但在生成的代码中会被擦除,因此它不会占用结构中的任何内存(这就是它是幻像的原因)。

需要它的原因在RFC 738 on variance中有详细解释。我会尝试在这里给你一个更短的(希望是正确的)版本。

在 Rust 中,您可以在大多数情况下(但并非总是如此!)在预期较短的情况下使用较长的生命周期。

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)

RFC 解释了为什么 Cell 期望完全相同(而不是更长)的生命周期,但现在我建议您相信编译器,允许 foo2 编译是不安全的。

现在假装你有一个

struct Foo<T> { t: T }

T 可以是任何东西,包括保存引用的类型。
特别是,T 可以是 &amp; i32 之类的类型,也可以是 &amp;Cell&lt;&amp;i32&gt; 之类的类型。
与我们上面的 foo 函数一样,Rust 可以通过检查 T (playground) 的类型来推断它可以或不能允许我们分配更短的生命周期。

但是,当您有一个未使用的类型参数时,推理没有任何需要检查的字段来了解它应该如何允许该类型在生命周期内表现。

如果你有

struct Foo<T>; // unused type parameter!

Rust 要求您使用 PhantomType 指定您是否希望您的 T 表现得像 &amp; i32Cell。你会写:

struct Foo<T> {
    marker: PhantomData<T>, // this is what you usually want
                            // unless you're working with unsafe code and
                            // raw pointers
}

或者你可以写:

struct Foo<T> {
    marker: PhantomData<Cell<T>>
}

【讨论】:

  • 在第一个示例中,我认为您应该使用PhantomData&lt;fn(I)&gt;。我会这样做以确保它是正确的。
  • @bluss 好的,这让我有点头疼,但我认为你已经有了具体的f: F,所以你不需要PhantomData 来告诉I 表现得像闭包的论据。你试图约束的是我自己。我不确定此时您是否应该决定要与Bar 一起使用的闭包。 (即,如果我打算使用像 Fn(Cell&lt;&amp;i32&gt;) 这样的闭包,我是否需要 PhantomType&lt;Cell&lt;&amp;i32&gt;&gt; 或者在这种情况下没有区别?)
  • 当然,我的头也很痛,但我认为fn(I) 和普通II 有相反的差异。我的意思是即使它们相同,我也知道fn(I) 是对的,因为这就是类型的行为方式——就像需要I 的函数一样。 :-)
  • 我认为故障基本上是这样的。根据类型变量X,我们有协变:Xfn() -&gt; X,逆变:fn(X),不变量:Cell&lt;X&gt;
【解决方案2】:

通过 trait 对象使用动态调度的另一种方法是为此使用 std::marker::PhantomData

use std::marker::PhantomData;

struct Bar<I, T, F>
where
    F: Fn(I) -> Option<T>,
{
    f: F,
    _i: PhantomData<I>,
    _t: PhantomData<T>,
}

(playground)

您只需使用PhantomData“实例化”PhantomData,例如

let phantom: PhantomData<T> = PhantomData;

【讨论】:

  • 你能解释一下为什么在这种情况下需要PhantomData吗?文档说“一个类型的行为就好像它存储了一个类型为 T 的值,即使它没有”,但结构 Bar 不应该存储任何类型为 I 或 @ 987654331@好像。
  • 我真的不知道。我认为这是类型系统的限制。此外,查看“所有权”部分,最好将其设为PhantomData&lt;*const T&gt;,尽管我不确定。
  • 你不需要_t,只要_i就足够了(问题是问为什么会这样)
  • 在这种情况下,PhantomData 将使结构使用的IT 有效地删除未使用的类型参数错误,而不会增加结构大小的任何开销。
猜你喜欢
  • 1970-01-01
  • 2021-07-31
  • 2019-11-10
  • 1970-01-01
  • 2021-07-29
  • 2011-08-06
  • 2019-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多