在通用关联类型在Rust中可用之前,会问这个问题,尽管它们是proposed和developed。
我的理解是特质泛型和关联类型可以绑定到结构的类型数量有所不同。
泛型可以绑定任意数量的类型:
struct Struct;
trait Generic<G> {
fn generic(&self, generic: G);
}
impl<G> Generic<G> for Struct {
fn generic(&self, _: G) {}
}
fn main() {
Struct.generic(1);
Struct.generic("a");
}
关联类型仅绑定1种类型:
struct Struct;
trait Associated {
type Associated;
fn associated(&self, associated: Self::Associated);
}
impl Associated for Struct {
type Associated = u32;
fn associated(&self, _: Self::Associated) {}
}
fn main() {
Struct.associated(1);
// Struct.associated("a"); // `expected u32, found reference`
}
通用关联类型是这两种的混合。它们绑定到一个类型恰好与1个关联的生成器,该生成器又可以关联任何数量的类型。那么,上一个示例中的Generic
与该通用关联类型有什么区别?
struct Struct;
trait GenericAssociated {
type GenericAssociated;
fn associated(&self, associated: Self::GenericAssociated);
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
fn associated(&self, _: Self::GenericAssociated) {}
}
答案 0 :(得分:8)
通用关联类型(GAT)是关联类型,它们本身是通用。 RFC以motivating example(强调我的名字)开头:
将以下特征视为典型的激励例子:
trait StreamingIterator { type Item<'a>; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; }
此特征非常有用-它允许一种Iterator 产生具有与寿命相关的寿命的值 引用传递给
next
。一个特别明显的用例 特质将是向量上的迭代器,该向量会产生重叠, 每次迭代都有可变的子切片。使用标准的Iterator
接口,这样的实现将是无效的,因为每个 切片将需要与迭代器一样长的时间存在,而不是 比next
发起的借贷期限更长。今天无法在Rust中表达该特征,因为 它取决于一种更高种类的多态性。该RFC将 将Rust扩展为包括那种特定形式的更高种类 多态性,在此称为关联类型 构造函数。此功能有许多应用程序,但是 主要应用程序与
StreamingIterator
特征:定义产生具有以下特征的类型的特征 寿命取决于接收者类型的本地借用。
请注意,关联的类型Item
如何具有通用生存期'a
。 RFC中的大多数示例都使用生存期,但是还有an example using a generic type:
trait PointerFamily { type Pointer<T>: Deref<Target = T>; fn new<T>(value: T) -> Self::Pointer<T>; }
请注意关联类型Pointer
如何具有通用类型T
。
上一个示例中的
Generic
与该通用关联类型之间有什么区别
可能没有任何问题,而且GAT的存在对您的情况没有帮助,这似乎并不要求关联类型本身就是通用的。
答案 1 :(得分:6)
让我们再次看看您的最后一个示例(我简称为):
trait GenericAssociated {
type GenericAssociated;
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
}
此功能不具有通用的关联类型!您只是在impl
块上有一个通用类型,您将其分配给关联的类型。嗯,好的,我可以看到混乱的来源。
您的示例错误“类型参数G
不受impl特质,自身类型或谓词的约束”。实施GAT时,这种情况不会改变,因为,这又与GAT无关。
在您的示例中使用GAT可能如下所示:
trait Associated {
type Associated<T>; // <-- note the `<T>`! The type itself is
// generic over another type!
// Here we can use our GAT with different concrete types
fn user_choosen<X>(&self, v: X) -> Self::Associated<X>;
fn fixed(&self, b: bool) -> Self::Associated<bool>;
}
impl Associated for Struct {
// When assigning a type, we can use that generic parameter `T`. So in fact,
// we are only assigning a type constructor.
type Associated<T> = Option<T>;
fn user_choosen<X>(&self, v: X) -> Self::Associated<X> {
Some(x)
}
fn fixed(&self, b: bool) -> Self::Associated<bool> {
Some(b)
}
}
fn main() {
Struct.user_choosen(1); // results in `Option<i32>`
Struct.user_choosen("a"); // results in `Option<&str>`
Struct.fixed(true); // results in `Option<bool>`
Struct.fixed(1); // error
}
但要回答您的主要问题:
特质的泛型和泛型关联类型有什么区别?
简而言之:它们允许延迟具体类型(或寿命)的应用,这会使整个类型系统更强大。
the RFC中有许多激励性的示例,其中最引人注目的是流迭代器和指针族示例。让我们快速了解一下为什么不能使用特征上的泛型实现流式迭代器。
流式迭代器的GAT版本如下:
trait Iterator {
type Item<'a>;
fn next(&self) -> Option<Self::Item<'_>>;
}
在当前的Rust中,我们可以将lifetime参数放在特征上,而不是关联的类型上:
trait Iterator<'a> {
type Item;
fn next(&'a self) -> Option<Self::Item>;
}
到目前为止,效果很好:所有迭代器都可以像以前一样实现此特征。但是,如果我们想使用它呢?
fn count<I: Iterator<'???>>(it: I) -> usize {
let mut count = 0;
while let Some(_) = it.next() {
count += 1;
}
count
}
我们应该注释哪个生命?除了注释'static
的生命周期,我们还有两个选择:
fn count<'a, I: Iterator<'a>>(it: I)
:这不会起作用,因为调用者选择了函数的通用类型。但是it
(在self
调用中将变为next
)位于我们的堆栈帧中。这意味着it
的生存期对于调用者是未知的。这样就得到了一个编译器(Playground)。这不是一个选择。fn count<I: for<'a> Iterator<'a>>(it: I)
(使用HRTB):这似乎可行,但是存在一些细微的问题。现在我们需要I
在 any 生存期Iterator
内实施'a
。对于许多迭代器而言,这不是问题,但是某些迭代器返回的项不会永远存在,因此它们无法在 any 生命周期内实现Iterator
,而生命周期短于其项目。使用这些排名较高的特征界限通常会导致秘密的'static
界限,这是非常严格的。因此,这也不总是可行。如您所见:我们无法正确写下I
的界限。实际上,我们甚至不想在count
函数签名中提及生命周期!没必要。这正是GAT允许我们做的事情(除其他事项外)。借助GAT,我们可以编写:
fn count<I: Iterator>(it: I) { ... }
它会起作用。因为只有在调用next
时才会发生“具体生命周期的应用”。
如果您对更多信息感兴趣,可以看看my blog post “ Solving the Generalized Streaming Iterator Problem without GATs”,在这里我尝试对特征使用泛型来解决缺少GAT的问题。还有(剧透):通常不起作用。