Generics 在Rust中依赖于另一个泛型的泛型类型

Generics 在Rust中依赖于另一个泛型的泛型类型,generics,types,rust,Generics,Types,Rust,我试图创建一个泛型结构,该结构具有泛型实现特征的边界。这种特性本身就是通用的。这是锈1.49.0 如果我这样做: trait Foo<T> {} struct Baz<F: Foo<T>> { x: F, } 然后我得到一个编译器错误,因为T未使用 唯一的选择似乎是包含一个PhantomData字段,但如果我的泛型依赖性变得更复杂,这将变得更难处理: use std::marker::PhantomData; trait Foo<T>

我试图创建一个泛型结构,该结构具有泛型实现特征的边界。这种特性本身就是通用的。这是锈1.49.0

如果我这样做:

trait Foo<T> {}

struct Baz<F: Foo<T>> {
    x: F,
}
然后我得到一个编译器错误,因为T未使用

唯一的选择似乎是包含一个PhantomData字段,但如果我的泛型依赖性变得更复杂,这将变得更难处理:

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不能检测到Baz中的T是实际使用的呢

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

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

结构Baz{ x:F, y:G, _t:幻影数据, } 为什么Rust无法检测到Baz中的T实际使用

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

您可以了解有关PhantomData的更多信息,以及它如何影响数据中的方差

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

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

结构Baz{ x:F, y:G, _t:幻影数据, } 为什么Rust无法检测到Baz中的T实际使用

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


您可以了解更多关于PhantomData的信息,以及它如何影响数据中的方差。

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

如果每个结构F可以用几种方式实现同一个F的Foo、Foo等,只需从结构Baz中删除绑定即可。T的具体选择在您决定使用它之前并不相关,但是附加一个绑定会迫使您选择一个T,该结构将为其工作,即使F将与多个类型一起工作

相反,只将参数和F:Foo绑定放在使用T的impl块上,请参见

如果每个结构F只希望以一种方式实现Foo,那么将T设为Foo的关联类型,而不是泛型类型。看

您可能不应该使用幻影数据。正如Ibraheem的回答正确提到的那样,这种标记类型不仅用于方差,还用于确定Baz是否在逻辑上包含T,以及由编译器确定应该为Baz实现什么。如果您不小心如何使用它,您可能会意外地以微妙的方式过度约束或欠约束API,请参见一个示例。更糟糕的是,由于类型的这些属性在外部是可见的,所以PhantomData的类型参数的选择可能会暴露给外部代码,这使得修复任何错误都成为一个破坏性的更改


如果您不知道PhantomData、PhantomData、PhantomData t>、PhantomData t>、PhantomData或PhantomData中的哪一个使用六种有意义的不同事物,并且此列表并不详尽,您应该通读上面的链接,并尝试确定哪种差异,关于T,Baz应该具有drop行为和auto-trait行为。这些是了解泛型结构的重要特性,它们是其外部API的一部分。如果其中任何一个对您的用例没有意义,这可能意味着Baz不应该是t之上的泛型。

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

如果每个结构F可以用几种方式实现同一个F的Foo、Foo等,只需从结构Baz中删除绑定即可。T的具体选择在您决定使用它之前并不相关,但是附加一个绑定会迫使您选择一个T,该结构将为其工作,即使F将与多个类型一起工作

相反,只将参数和F:Foo绑定放在使用T的impl块上,请参见

如果每个结构F只希望以一种方式实现Foo,那么将T设为Foo的关联类型,而不是泛型类型。看

您可能不应该使用幻影数据。正如Ibraheem的回答正确提到的那样,这种标记类型不仅用于方差,还用于确定Baz是否在逻辑上包含T,以及由编译器确定应该为Baz实现什么。如果您不小心如何使用它,您可能会意外地以微妙的方式过度约束或欠约束API,请参见一个示例。更糟糕的是,由于类型的这些属性在外部是可见的,所以PhantomData的类型参数的选择可能会暴露给外部代码,这使得修复任何错误都成为一个破坏性的更改

如果您不知道PhantomData、PhantomData、PhantomData t>、PhantomData t>、PhantomData或PhantomData中的哪一个要使用六个me
一个完全不同的东西,并且这个列表不是详尽的,你应该通读上面的链接,并尝试确定Baz对于T应该有哪种差异、丢弃行为和自动特征行为。这些是了解泛型结构的重要品质,它们是它的外部API的一部分。如果其中任何一个对您的用例没有意义,这可能意味着Baz根本不应该是t之上的泛型。

啊,非常有用。谢谢当我创建一个结构实例时,是否有必要避免键入幻影数据?i、 e.我是否可以删除Baz{x:foo1,y:foo2,_t:PhantomData}中的最后一个字段?@HarryBraviner不,你必须定义它。但是如果您派生Defaultrustc喜欢,您可以执行类似于Baz{x:foo1,y:foo2,…Self::default}的操作。。。。上面写着:预期标识符。@LukeSkywalker我错过了开头的括号,谢谢你指出。啊,非常有用。谢谢当我创建一个结构实例时,是否有必要避免键入幻影数据?i、 e.我是否可以删除Baz{x:foo1,y:foo2,_t:PhantomData}中的最后一个字段?@HarryBraviner不,你必须定义它。但是如果您派生Defaultrustc喜欢,您可以执行类似于Baz{x:foo1,y:foo2,…Self::default}的操作。。。。它说:预期的标识符。@LukeSkywalker我错过了开头的括号,谢谢你指出了这一点。如果实现Foo并不重要,为什么有必要实现Foo呢?如果F同时实现了Foo和Foo,那么T的选择对Baz有什么影响呢?首先,我不能只执行structbaz{x:F},因为我得到了一个编译器错误。但一般来说,我可能想用一个界来约束T。或者我可能有一个类似于Baz的结构定义。我没有给t设置任何边界,但是F和G可能不同,它们必须为相同的t实现Foo。我的意思是结构Baz{x:F}。在任何情况下,如果您确实选择了PhantomData路线,那么了解什么样的幻影困扰着您是很重要的。我已经添加了一个答案。如果F为哪个t实现它并不重要,那么为什么有必要实现Foo呢?如果F同时实现了Foo和Foo,那么T的选择对Baz有什么影响呢?首先,我不能只执行structbaz{x:F},因为我得到了一个编译器错误。但一般来说,我可能想用一个界来约束T。或者我可能有一个类似于Baz的结构定义。我没有给t设置任何边界,但是F和G可能不同,它们必须为相同的t实现Foo。我的意思是结构Baz{x:F}。在任何情况下,如果您确实选择了PhantomData路线,那么了解什么样的幻影困扰着您是很重要的。我补充了一个答案。
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,
}