• Rust中的泛型关联类型(GATs)的用法
  • 发布于 2个月前
  • 326 热度
    0 评论
Rust引入了泛型关联类型(GATs)来解决某些泛型模式方面的限制。在GATs之前,Rust有关联类型,使类型与特征相关联。然而,这些并没有直接绑定到实现类型的泛型参数上。GATs增强了这一概念,它允许关联类型依赖于trait的泛型参数。

GATs在很多情况下都很有用,包括迭代器模式和Futures以及异步编程。在这里,异步库可以利用GATs来定义具有关联类型的特征,这些类型准确地表示了这些依赖关系,并为异步操作提供了更精确的类型信息。

在本文中,我们将探索在Rust中使用GATs来改进代码和应用程序的性能。

开始学习Rust泛型关联类型
Rust特征可以有关联的类型。然而,在GATs之前,这些相关类型不能有自己的泛型参数。GATs取消了这一限制,允许关联类型是泛型的,并且具有生命周期或类型参数。
trait Producer {
    type Item;
    fn produce(&self) -> Self::Item;
}
Producer trait有一个produce()方法,该方法接受self的引用并返回self:: item类型。Self::Item语法引用了在trait中定义的关联类型。

使用GATs,你可以扩展trait,允许关联的类型有一个泛型参数:
trait Producer {
    type Item<T>;
    fn produce<T>(&self, value: T) -> Self::Item<T>;
}
在这个例子中,Producer trait声明了produce方法,该方法接受类型为T的值作为参数,并返回类型Self::item<T>。语法Self::Item<T>引用了与实现类型相关联的Item<T>类型。GATs构成了Rust类型系统的基石,提供了以前无法达到的表达级别。它们允许编写更通用和可重用的代码,减少样板文件并增强代码的可维护性。

在Rust中何时使用GATs
当需要在Rust中表达类型和生命周期之间的复杂关系时,GATs非常方便。当设计需要返回生命周期绑定到self的类型的函数或创建泛型数据结构时,它们特别有用。

考虑这样一个场景:你正在创建一个数据结构,该结构包含一组项,并在这些项上提供一个迭代器。如果没有GATs,将不得不使用Box<dyn Iterator>,这将增加运行时成本。

使用GATs,可以这样自然地表达:
trait Iterable {
    type Item;
    type Iterator<'a>: Iterator<Item = &'a Self::Item> where Self: 'a;
    // 堆代码 duidaima.com
    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
这里,iterable trait有两个关联类型——Item和Iterator。Item关联类型表示可迭代元素的类型,Iterator泛型关联类型表示iter方法返回的Iterator类型。


在Rust程序中使用GATs的用例
在日常Rust程序中使用GATs有几个原因和用例,包括避免不必要的内存分配,支持更有效的代码生成,以及改进代码的人体工程学。下面让我们更详细地探讨其中的每一个。

避免不必要的内存分配
GATs的一个日常用途是避免不必要的内存分配。通过为迭代器定义一个trait,在必要时才分配内存,可以防止不必要的内存分配。下面是一个trait,它定义了一个迭代器,迭代器可以遍历集合中的元素,并为使用的元素分配内存:
trait LendingIterator {
    type Item<'a>: 'a where Self: 'a;
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

struct MyVec<T>(Vec<T>);

impl<T> LendingIterator for MyVec<T> where T: 'static {
    type Item<'a> = &'a T;

    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
        if self.0.is_empty() {
            None
        } else {
            Some(&self.0[0])
        }
    }
}

fn main() {
    let mut my_vec = MyVec(vec![1, 2, 3, 4, 5]);

    while let Some(item) = my_vec.next() {
        println!("{}", item);
        my_vec.0.remove(0);
    }
}
上面的Rust程序用GATs实现了一个自定义迭代器。LendingIterator trait模仿迭代器的行为。MyVec类型实现了LendingIterator特性,main函数创建了一个MyVec,并在从vector中删除元素之前打印元素。

更有效地生成代码
GATs可以帮助更有效地生成代码,可以使用GATS为通用数据结构定义一个特性,然后在程序中为使用的数据结构生成代码。让我们通过一个例子来更好地理解这个概念。

下面是一个trait,它定义了一个表示树的通用数据结构:
trait Tree {
    type Item<'a>: 'a where Self: 'a;
    type Left<'a>: 'a where Self: 'a;
    type Right<'a>: 'a where Self: 'a;

    fn root(&self) -> &Self::Item<'_>;
    fn left(&self) -> Option<&Self::Left<'_>>;
    fn right(&self) -> Option<&Self::Right<'_>>;
}
Tree特征表示树中元素的类型。root()方法返回对树根的引用。left()和right()方法分别返回对树的左子树和右子树的引用。Tree特征不生成任何代码。相反,它将任务留给实现trait的类型,由它来决定要实现的方法。

通过使用泛型关联类型,Tree特征允许在树数据结构中灵活地定义根和左右子树的类型。Tree特征的实现类型将决定Item、Left和Right的特定类型。这种方法可以帮助你为各种树状数据结构生成更高效的代码。

改进代码的人体工程学
可以使用GATs以多种方式改进代码的人体工程学,可以为泛型构建器定义一个trait,以简化复杂数据结构的创建。下面是一个trait,它定义了一个通用的创建树的构造器:
trait TreeBuilder<T> {
    type Tree<'a>;

    fn root<'a>(self, value: T) -> Self::Tree<'a>;
    fn left<'a>(self, value: T) -> Self::Tree<'a>;
    fn right<'a>(self, value: T) -> Self::Tree<'a>;
    fn build<'a>(self) -> Self::Tree<'a>;
}
可以使用TreeBuilder特征轻松创建复杂的树,该特性提供了用于创建具有一个根值的树以及左子树和右子树的方法,构造方法返回对树的引用。

总结
在本文中,我们讨论了Rust中的泛型关联类型(GATs)。我们探讨了它们的一些用例,以及如何使用它们来提高Rust应用程序的整体性能。GATs提供了一种更具表现力和更强大的方式,来为Rust中的泛型和关联类型之间的关系进行建模。它们支持更精确的类型信息,并帮助你在迭代器设计、异步编程等领域实现高级模式和库。

用户评论