特征的通用类型和通用关联类型之间有什么区别? [英] What's the difference between a trait's generic type and a generic associated type?

查看:63
本文介绍了特征的通用类型和通用关联类型之间有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尽管通用关联类型是建议已开发.

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): this won't work because generic types of a function are choosen by the caller. But the it (which will become self in the next call) lives in our stack frame. This means that the lifetime of it 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 require I to implement Iterator 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 implement Iterator 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆