繁体   English   中英

特质的泛型类型和通用的相关类型之间有什么区别?

[英]What's the difference between a trait's generic type and a generic associated type?

在Rust中提供通用关联类型之前会询问此问题,尽管它们是提出开发的

我的理解是,特质泛型和相关类型在它们可以绑定到结构的类型数量上有所不同。

泛型可以绑定任意数量的类型:

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) {}
}

有什么不同?

通用关联类型(GAT)是相关类型 ,它们本身是通用的 RFC以一个激励性的例子开始,强调我的:

考虑以下特征作为代表性激励示例:

 trait StreamingIterator { type Item<'a>; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; } 

这个特性非常有用 - 它允许一种迭代器产生的值,这些值的寿命与传递给next的引用的生命周期相关。 该特征的一个特别明显的用例是在向量上的迭代器,其在每次迭代时产生重叠的,可变的子序列。 使用标准的Iterator接口,这样的实现将是无效的,因为每个切片都需要与迭代器一样长,而不是只要next启动的借用。

这种特性不能用Rust表达 ,因为它依赖于一种更高级的多态性。 此RFC将扩展Rust以包括特定形式的高级多态性,这里将其称为关联类型构造函数。 此功能有许多应用程序,但主要应用程序与StreamingIterator特征的行相同:定义特征,这些特征产生的类型的生命周期与接收器类型的本地借用相关联。

注意关联类型Item如何具有通用生命周期'a RFC中的大多数示例都使用生命周期,但也有使用泛型类型的示例

 trait PointerFamily { type Pointer<T>: Deref<Target = T>; fn new<T>(value: T) -> Self::Pointer<T>; } 

注意关联类型Pointer如何具有泛型类型T

你的具体例子

上一个示例中的Generic与此通用关联类型之间的区别是什么

可能没有,GAT的存在对你的情况没有帮助,这似乎不需要一个本身就是通用的相关类型。

让我们再看看你的最后一个例子(由我缩短):

trait GenericAssociated {
    type GenericAssociated;
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;
}

配备通用关联类型! 您只是在impl块上使用泛型类型,并将其分配给关联类型。 嗯,好的,我可以看到混乱的来源。

“类型参数G不受impl trait,self type或谓词约束”的示例错误。 实施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
}

但是回答你的主要问题:

特质的泛型类型和通用的相关类型之间有什么区别?

简而言之: 它们允许延迟具体类型(或寿命)的应用,这使得整个类型系统更加强大。

RFC中有许多动机示例,最值得注意的是流迭代器和指针族示例。 让我们快速了解为什么无法使用特征上的泛型实现流式迭代器。

流式迭代器的GAT版本如下所示:

trait Iterator {
    type Item<'a>;
    fn next(&self) -> Option<Self::Item<'_>>;
}

在当前的Rust中,我们可以将life参数放在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 'a 这对于许多迭代器来说不是问题,但是一些迭代器会返回永远不会生命的项目,因此它们无法在任何生命周期内实现Iterator - 只比生命周期短。 使用这些排名较高的特征界限往往导致秘密的'static边界”,这是非常有限的。 所以这也并不总是有效。

正如你所看到的:我们无法正确地记下I的界限。 实际上,我们甚至不想提及count函数签名中的生命周期! 它没有必要。 这正是GAT允许我们做的事情(除其他事项外)。 通过GAT,我们可以写:

fn count<I: Iterator>(it: I) { ... }

它会起作用。 因为“具体生命的应用”只在我们next打电话时才会发生。

如果您对更多信息感兴趣,可以查看我的博客文章“解决没有GAT的广义流式迭代器问题” ,我尝试在特性上使用泛型类型来解决缺少GAT问题。 而且(剧透):它通常不起作用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM