特征的通用类型和通用关联类型之间有什么区别?

时间:2019-02-20 17:01:13

标签: generics rust generic-programming

在通用关联类型在Rust中可用之前,会问这个问题,尽管它们是proposeddeveloped

我的理解是特质泛型和关联类型可以绑定到结构的类型数量有所不同。

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

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

2 个答案:

答案 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的问题。还有(剧透):通常不起作用。