特征的通用类型和通用关联类型之间有什么区别? [英] What's the difference between a trait's generic type and a generic associated type?
问题描述
This question is asked before generic associated types are available in Rust, although they are proposed and developed.
我的理解是,特征泛型和关联类型在可绑定到结构的类型数量上有所不同.
My understanding is that trait generics and associated types differ in the number of types which they can bind to a struct.
泛型可以绑定任意数量的类型:
Generics can bind any number of types:
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
与该通用关联类型之间有什么区别?
Generic associated types are a mix of these two. They bind to a type exactly 1 associated generator, which in turn can associate any number of types. Then what is the difference between Generic
from the previous example and this generic associated type?
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) {}
}
推荐答案
让我们再次看看您的最后一个示例(我简称为):
Let's take a look at your last example again (shortened by me):
trait GenericAssociated {
type GenericAssociated;
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
}
不没有通用关联类型的功能!您只是在impl
块上有一个通用类型,您已将该通用类型分配给关联的类型.嗯,好的,我可以看到混乱的根源.
This does not feature generic associated types! You are just having a generic type on your impl
block which you assign to the associated type. Mh, ok, I can see where the confusion comes from.
您的示例错误类型参数G
不受impl特性,自身类型或谓词的约束".实施GAT时,这种情况不会改变,因为,这又与GAT无关.
Your example errors with "the type parameter G
is not constrained by the impl trait, self type, or predicates". This won't change when GATs are implemented, because, again, this has nothing to do with GATs.
在您的示例中使用GAT可能如下所示:
Using GATs in your example could look like this:
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
}
但要回答您的主要问题:
But to answer you main question:
特征的通用类型和通用关联类型之间有什么区别?
What's the difference between a trait's generic type and a generic associated type?
简而言之:它们允许延迟具体类型(或寿命)的应用,这会使整个类型系统更强大.
RFC ,最值得注意的是流式迭代器和指针家族示例.让我们快速了解一下为什么不能在特征上使用泛型实现流式迭代器.
There are many motivational examples in the RFC, most notably the streaming iterator and the pointer family example. Let's quickly see why the streaming iterator cannot be implemented with generics on the trait.
流式迭代器的GAT版本如下:
The GAT version of the streaming iterator looks like this:
trait Iterator {
type Item<'a>;
fn next(&self) -> Option<Self::Item<'_>>;
}
在当前的Rust中,我们可以将lifetime参数放在特征上,而不是关联的类型上:
In current Rust, we could put the lifetime parameter on the trait instead of the associated type:
trait Iterator<'a> {
type Item;
fn next(&'a self) -> Option<Self::Item>;
}
到目前为止,效果很好:所有迭代器都可以像以前一样实现此特征.但是,如果我们想使用它呢?
So far so good: all iterators can implement this trait as before. But what if we want to use it?
fn count<I: Iterator<'???>>(it: I) -> usize {
let mut count = 0;
while let Some(_) = it.next() {
count += 1;
}
count
}
我们应该注释哪个生命?除了注释'static
寿命,我们还有两种选择:
What lifetime are we supposed to annotate? Apart from annotating the 'static
lifetime, we have two choices:
-
fn count<'a, I: Iterator<'a>>(it: I)
:这将不起作用,因为调用者选择了函数的泛型类型.但是it
(将在next
调用中变为self
)存在于我们的堆栈框架中.这意味着it
的生存期对于调用者是未知的.因此,我们得到了一个编译器(游乐场).这不是一个选择. -
fn count<I: for<'a> Iterator<'a>>(it: I)
(使用HRTB):这似乎可行,但是存在一些细微的问题.现在,我们需要I
在 any 生存期'a
内实现Iterator
.对于许多迭代器来说,这不是问题,但是某些迭代器返回的项不会永远存在,因此它们无法在 any 生存期内实现Iterator
,而生存期短于其项目.使用这些排名较高的特征范围通常会导致非常严格的'static
秘密范围.因此,这也不总是可行.
fn count<'a, I: Iterator<'a>>(it: I)
: this won't work because generic types of a function are choosen by the caller. But theit
(which will becomeself
in thenext
call) lives in our stack frame. This means that the lifetime ofit
is not known to the caller. Thus we get a compiler (Playground). This is not an option.fn count<I: for<'a> Iterator<'a>>(it: I)
(using HRTBs): this seems to work, but it has subtle problems. Now we requireI
to implementIterator
for any lifetime'a
. This is not a problem with many iterators, but some iterators return items that don't life forever and thus they cannot implementIterator
for any lifetime -- just lifetimes shorter than their item. Using these higher ranked trait bounds often leads to secret'static
bounds which are very restricting. So this also doesn't always work.
如您所见:我们无法正确写下I
的界限.实际上,我们甚至不想在count
函数签名中提及生命周期!没必要.这正是GAT允许我们做的事情(除其他事项外).借助GAT,我们可以编写:
As you can see: we cannot properly write down the bound of I
. And actually, we don't even want to mention the lifetime in the count
function's signature! It shouldn't be necessary. And that's exactly what GATs allow us to do (among other things). With GATs we could write:
fn count<I: Iterator>(it: I) { ... }
它会起作用.因为具体生命周期的应用"仅在我们调用next
时发生.
And it would work. Because the "application of a concrete lifetime" only happens when we call next
.
如果您对更多信息感兴趣,可以查看,我尝试在特征上使用泛型来解决GAT的不足.还有(剧透):通常不起作用.
If you are interested in even more information, you can take a look at my blog post " Solving the Generalized Streaming Iterator Problem without GATs" where I try using generic types on traits to work around the lack of GATs. And (spoiler): it usually doesn't work.
这篇关于特征的通用类型和通用关联类型之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!