【问题标题】:Generic types that depend on another generic in Rust依赖于 Rust 中另一个泛型的泛型类型
【发布时间】:2021-01-29 19:45:35
【问题描述】:

我正在尝试创建一个泛型结构,并具有泛型实现特征的界限。该特征本身是通用的。这是在 Rust 1.49.0 中。

如果我这样做:

trait Foo<T> {}

struct Baz<F: Foo<T>> {
    x: F,
}

我收到编译错误,因为 T 未定义。但如果我定义它:

trait Foo<T> {}

struct Baz<T, F: Foo<T>> {
    x: F,
}

然后我得到一个编译器错误,因为 T 未使用。

唯一的选择似乎是包含一个PhantomData&lt;T&gt; 字段,但如果我的通用依赖变得更加复杂,这将开始变得更加笨拙:

use std::marker::PhantomData;

trait Foo<T> {}

struct Baz<T, U, F: Foo<T>, G: Foo<U>> {
    phantom_t: PhantomData<T>,
    phantom_u: PhantomData<U>,
    x: F,
    y: G,
}

我的一半领域是幻影!该结构实际​​上是闹鬼的。

我的问题是:最后编译的示例真的是惯用的 Rust 吗?如果是这样,为什么 Rust 不能检测到 Baz&lt;T, Foo&lt;T&gt;&gt; 中的 T 确实被使用了?

【问题讨论】:

  • 为什么F 实现Foo&lt;T&gt; 是必要的,如果它为哪个T实现它并不重要?如果F同时实现Foo&lt;i32&gt;和Foo&lt;u32&gt;,T的选择对Baz&lt;F&gt;有何影响?
  • 首先我不能只做struct Baz&lt;F: Foo&lt;T&gt;&gt; { x: F},因为我得到一个编译器错误。但总的来说,我可能想限制T。或者我可能有一个看起来像Baz&lt;F: Foo&lt;T&gt;, G: Foo&lt;T&gt;&gt; 的结构定义。我对T 没有任何限制,但F 和G(可能不同)必须为相同 T 实现Foo&lt;T&gt;。
  • struct Baz&lt;F&gt; { x: F } 是我的意思。在任何情况下,如果您确实走PhantomData 路线,重要的是要知道什么样的幻影困扰着您。我已经添加了答案。

标签: generics types rust


【解决方案1】:

最后编译的例子真的是惯用的 Rust 吗?

存储多个幻像类型参数的惯用方法是使用元组:

struct Baz<T, U, F: Foo<T>, G: Foo<U>> {
    x: F,
    y: G,
    _t: PhantomData<(T, U)>,
}

为什么 Rust 检测不到 Baz&lt;T, Foo&lt;T&gt;&gt; 中的 T 实际被使用了?

由于variance 和drop check,这实际上是预期的行为。这里的想法是编译器需要知道它可以对类型参数T 施加什么约束,PhantomData 类型的使用将指导编译器如何做到这一点。

您可以详细了解PhantomData 以及它如何影响Rust nomicon 中的差异。

【讨论】:

  • 啊,很有帮助。谢谢!当我创建结构的实例时,是否有避免输入PhantomData?即我可以以某种方式删除Baz {x: foo1, y: foo2, _t: PhantomData} 中的最后一个字段吗?
  • @HarryBraviner 不,你必须定义它。但是如果你派生Default,你可以做类似Baz {x: foo1, y: foo2, ...Self::default()}的事情
  • rustc 确实喜欢...。它说:expected identifier.
  • @LukeSkywalker 我错过了左括号,感谢您指出这一点。
【解决方案2】:

您可能应该做以下事情之一:

  1. 如果每个结构F 可以通过多种方式实现Foo(Foo&lt;String&gt;、Foo&lt;i32&gt; 等相同F),只需从结构中删除绑定Baz。 T 的具体选择在您决定使用它之前并不相关,但附加一个绑定会强制您选择一个结构将适用的 T,即使 F 将适用于多种类型。

    相反,将&lt;T&gt; 参数和F: Foo&lt;T&gt; 绑定仅放在使用T 的impl 块上(请参阅Should trait bounds be duplicated in struct and impl?)。

  2. 如果每个结构 F 预计仅以一种方式实现 Foo,则将 T 设为 Foo 的关联类型,而不是泛型类型。见When is it appropriate to use an associated type versus a generic type?

您应该可能不使用PhantomData&lt;T&gt;。这种标记类型不仅用于方差,正如 Ibraheem 的回答正确提到的那样,而且由 drop checker 确定 Baz&lt;T&gt; 是否在逻辑上包含 T,并由编译器确定应该为什么实现 auto traits Baz&lt;T&gt;。如果您不小心如何使用它,您可能会意外地以微妙的方式过度约束或约束您的 API(参见 Making a struct outlive a parameter given to a method of that struct 的一个示例)。更糟糕的是,由于类型的这些属性在外部可见,PhantomData 的类型参数的选择可能会暴露给外部代码,这使得修复任何错误都成为重大更改。

如果您不知道要使用PhantomData&lt;T&gt;、PhantomData&lt;fn(T)&gt;、PhantomData&lt;fn() -&gt; T&gt;、PhantomData&lt;fn(T) -&gt; T&gt;、PhantomData&lt;*const T&gt; 或 PhantomData&lt;*mut T&gt; 中的哪一个(六个有意义的不同事物,并且此列表并不详尽),您应该阅读上面的链接并尝试确定Baz 相对于T 应该具有哪种差异、丢弃行为和自动特征行为。这些是了解通用结构的重要品质,它们是其外部 API 的一部分。如果其中任何一个对您的用例没有意义,则可能意味着 Baz 根本不应该比 T 通用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-21
    • 1970-01-01
    • 2021-12-25
    • 2014-08-02
    • 2022-11-30
    • 1970-01-01
    • 2020-05-19
    • 2021-10-31
    相关资源
    最近更新 更多