trait 的泛型类型和泛型关联类型之间有啥区别?
Posted
技术标签:
【中文标题】trait 的泛型类型和泛型关联类型之间有啥区别?【英文标题】:What's the difference between a trait's generic type and a generic associated type?trait 的泛型类型和泛型关联类型之间有什么区别? 【发布时间】:2019-07-14 11:02:50 【问题描述】:这个问题是在 Rust 中可用的泛型关联类型之前提出的,尽管它们是 proposed 和 developed。
我的理解是 trait 泛型和关联类型在它们可以绑定到结构的类型数量上有所不同。
泛型可以绑定任意数量的类型:
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");
关联类型只绑定一种类型:
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)
【问题讨论】:
【参考方案1】:让我们再看看你的最后一个例子(被我缩短了):
trait GenericAssociated
type GenericAssociated;
impl<G> GenericAssociated for Struct
type GenericAssociated = G;
这不具有通用关联类型!您只是在分配给关联类型的 impl
块上有一个泛型类型。嗯,好的,我知道混乱来自哪里了。
您的示例错误“类型参数 G
不受 impl trait、self 类型或谓词的约束”。这在实施 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 中有很多激励性的示例,最值得注意的是流式迭代器和指针族示例。让我们快速了解为什么不能在 trait 上使用泛型实现流式迭代器。
流式迭代器的 GAT 版本如下所示:
trait Iterator
type Item<'a>;
fn next(&self) -> Option<Self::Item<'_>>;
在当前的 Rust 中,我们可以将生命周期参数放在 trait 上,而不是关联类型:
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
(在next
调用中将变为self
)存在于我们的 堆栈框架中。这意味着调用者不知道it
的生命周期。因此我们得到了一个编译器(Playground)。这不是一个选项。
fn count<I: for<'a> Iterator<'a>>(it: I)
(使用 HRTB):这似乎可行,但存在一些微妙的问题。现在我们需要I
来实现Iterator
的any 生命周期'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 不足的问题。而且(剧透):它通常不起作用。
【讨论】:
关于你提到的第二点fn count<I: for<'a> Iterator<'a>>(it: I)
——你能否提供一个例子说明它会因为限制而失败(即秘密'static
bound)?
@cotigao 我更详细地描述了限制in this blog post(链接有一个指向正确部分的锚)。简而言之:如果你想让你的项目引用一个具有迭代器生命周期的泛型(即impl<'s, T> Iterator<'s> for ... type Item = &'s T;
),你必须绑定T: 's
。否则 Rust 不会让你编写那个引用类型。边界for<'a> I: Iterator<'a>
加上T: 's
导致T: 'static
。我希望这会有所帮助!
@cotigao 哦,你可以看到一个具体的例子in this playground(也在博客文章中链接)。在最底部,我们不能将 WindowMut
迭代器传递给 count
,因为 T
不是 'static
。
感谢您的帖子!一个问题:引用你的文章; " for<'s> T: 's
相当于 T:'static
" ⸺ 是否相等,因为迭代器间接借用了T
(即通过本身借用生命周期为'a
的元素的切片)?
@cotigao 我不确定我是否理解你的问题。 for<'s> B
的意思是“对于所有可能的生命周期,绑定 B 必须保持”。该语句还包括“B必须为'static
保留”,对吗?因为那是“可能的一生”。由于'static
在某种意义上“包括”了所有其他生命周期,因此这两个边界是等价的。【参考方案2】:
有什么区别?
通用关联类型 (GAT) 是关联类型,它们本身就是通用。 RFC 以motivating example 开头,重点是我的:
将以下特质视为具有代表性的激励示例:
trait StreamingIterator type Item<'a>; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
这个特性非常有用 - 它允许一种迭代器 产生生命周期与生命周期相关的值 引用传递给
next
。一个特别明显的用例 特征将是对产生重叠的向量的迭代器, 每次迭代的可变子切片。使用标准Iterator
接口,这样的实现将是无效的,因为每个 只要迭代器存在,切片就必须存在,而不是 比next
发起的借用时间长。这个特性不能在今天的 Rust 中表达,因为 它依赖于一种更高种类的多态性。该 RFC 将 扩展 Rust 以包含更高种类的特定形式 多态性,这里称为关联类型 构造函数。此功能有许多应用程序,但 主要应用程序与
StreamingIterator
trait:定义产生类型的特征 与接收器类型的本地借用相关的生命周期。
请注意关联类型 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 的存在对您的情况没有帮助,这似乎不需要关联类型本身就是通用的。
【讨论】:
这使得“常规”泛型只是泛型关联类型的语法糖,不是吗?我有点惊讶 Rust 试图引入第二种方式来表达同样的事情,通常尽可能避免。 @CodeSandwich 我不确定我是否遵循。struct Foo
“语法糖”是否超过struct Foo<T>
?
那是……不太准确。关联类型允许 trait 实现者 选择一个类型,然后 trait 对其进行操作。泛型类型允许 被调用者 选择一个类型,该特征对其进行操作。关于谁选择以及何时选择的区别是这里差异的核心。更多信息请参阅问答here
GAT 允许 trait author 要求 implementors 选择满足某些约束的 generic 类型 - 这解决了一个问题作为当前系统的限制而出现的问题 - 答案中讨论了两个示例。
@CodeSandwich impl<T> Foo for Bar ...
总是不正确的,并且没有任何东西,无论是与 GAT 相关还是其他,您都可以放入 ...
以使其编译。卢卡斯的回答解释了原因。在 GAT 中,impl
是 not 泛型的,关联类型 itself 是。以上是关于trait 的泛型类型和泛型关联类型之间有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章