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

Cod*_*ich 7 generics generic-programming rust

在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");
}
Run Code Online (Sandbox Code Playgroud)

关联类型只绑定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`
}
Run Code Online (Sandbox Code Playgroud)

通用关联类型是这两者的混合.它们绑定到一个类型正好相关的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) {}
}
Run Code Online (Sandbox Code Playgroud)

Luk*_*odt 9

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

trait GenericAssociated {
    type GenericAssociated;
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;
}
Run Code Online (Sandbox Code Playgroud)

这并没有配备通用关联类型!您只是在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
}
Run Code Online (Sandbox Code Playgroud)

但是回答你的主要问题:

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

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

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

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

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

在当前的Rust中,我们可以将life参数放在trait而不是关联的类型上:

trait Iterator<'a> {
    type Item;
    fn next(&'a self) -> Option<Self::Item>;
}
Run Code Online (Sandbox Code Playgroud)

到目前为止一切顺利:所有迭代器都可以像以前一样实现这个特性.但是如果我们想要使用呢?

fn count<I: Iterator<'???>>(it: I) -> usize {
    let mut count = 0;
    while let Some(_) = it.next() {
        count += 1;
    }
    count
}
Run Code Online (Sandbox Code Playgroud)

我们应该注释什么生命周期?除了注释'static生命周期,我们有两个选择:

  • fn count<'a, I: Iterator<'a>>(it: I):这不起作用,因为调用者选择函数的泛型类型.但是it(将selfnext调用中)将存在于我们的堆栈框架中.这意味着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) { ... }
Run Code Online (Sandbox Code Playgroud)

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

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


She*_*ter 8

有什么不同?

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

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

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

这个特性非常有用 - 它允许一种Iterator,它产生的值与传递给它的引用的生命周期相关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>;
}
Run Code Online (Sandbox Code Playgroud)

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

你的具体例子

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

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

  • @CodeSandwich`insl <T> Foo for Bar {...}`总是不正确的,没有什么,GAT相关或其他,你可以放入`...`来编译.卢卡斯的答案解释了原因.在GAT中,`impl`是*not*泛型,关联类型*本身*是. (2认同)