这姓名好长!这是啥子哟?

不要紧,咱们从头理理。咱们先来温习一下Rust的语法结构。Rust程序是什么构成的?答案是:条目(item)。

每个Rust程序都是一条一条的,每个都是一个条目。比方你在main.rs里写一个结构体界说, 再写一个完成界说为它加两个办法,再写一个main函数。这便是crate对应的模块条目里的三个条目。

说完了条目,咱们再来说相关条目。相关条目不是条目!要点便是在“相关”俩字上,什么是相关呢? 其实便是“跟某个类型有关”,作用便是能够运用一个专门的关键字叫做Self。 这个关键词便是用来指代方才说的那个类型的。

相关条目能够界说在两处,一个是特质界说的花括号中,一个是完成界说的花括号中。

相关条目一共有三种:相关常数,相关函数,相关类型(别号);它们与条目中的三种:常数、函数、类型(别号) 一一对应。

举个栗子吧!


#![feature(generic_associated_types)]
#![allow(incomplete_features)]
const A: usize = 42;
fn b<T>() {}
type C<T> = Vec<T>;
trait X {
    const D: usize;
    fn e<T>();
    type F<T>; // 新加的便是这个!之前在这儿不能够写<T>
}
struct S;
impl X for S {
    const D: usize = 42;
    fn e<T>() {}
    type F<T> = Vec<T>;
}

这个有啥用咧?

蛮有用的,可是仅仅是在特定的场景之下。Rust社区里对泛型相关类型有两个用例是非常经典的,咱们先试着介绍它们一下。

可是在开端介绍之前,咱们还要再来温习一下泛型。泛型这个词英文是generic,其实是通用的意思。 泛型类型是什么呢?简略来说,便是缺点什么参数的类型,让运用的人填充。

趁便说一下,前人把它意译取名叫泛型,因为很多体系里能填的参数是类型。其实在Rust里边,不只是类型能够当泛型参数。 泛型参数有三种:类型、生存期、常数。

好,咱们来看一个具体的泛型类型的例子:Rc<T>,它是具有一个泛型参数的泛型类型。 泛型类型Rc不是类型哈,只要提供了这个泛型参数的“实参”,才是真正的类型,比方Rc<bool>

如果我写一个数据结构,里边要同享数据,可是我事前不知道运用者到底需要我在这儿用Rc仍是Arc,怎么办呢? 最简略的办法便是代码写两遍,这个听起来有点笨拙,的确也是如此,可是也是的确有效的。 随口一提,crates.io上有俩库im和im-rc代码主要区别便是里边用的是Arc仍是Rc。 实践上泛型相关类型就能够很好的处理这个问题,接下来, 咱们就来看泛型相关类型的第一个经典运用场景:类型宗族(type family)。

使命#1:用泛型相关类型支持类型宗族

好,现在咱们来做一个“挑选器”,让编译器根据这个挑选器来知道需要用的到底是Rc<T>仍是Arc<T>。代码长这样:


trait PointerFamily {
    type PointerType<T>;
}
struct RcPointer;
impl PointerFamily for RcPointer {
    type PointerType<T> = Rc<T>;
}
struct ArcPointer;
impl PointerFamily for ArcPointer {
    type PointerType<T> = Arc<T>;
}

挺简略的吧,这样你就界说了两个“挑选器”类型,能够用它来代表要用的是Rc仍是Arc。实践用用看:


struct MyDataStructure<T, PointerSel: PointerFamily> {
    data: PointerSel::PointerType<T>
}

这样你泛型参数用RcPointer或许ArcPointer就能够挑选实践的数据表明了。 有了这个功能,方才说的两个包就能够合成一个包了。好耶~

使命#2:用泛型相关类型完成流式处理迭代器

另一个问题其实是Rust比较特有的,其他言语里,要么不存在这个问题(古尔丹:代价是什么呢?), 要么,抛弃治疗这个问题(咳咳)。

这个问题是这样的,希望在API接口上表明输入值与输入值之间、输入值与输出值之间的依靠联系。 依靠联系并不是一个很简单表达出来的东西。Rust的方案是什么呢? 在Rust里,这个人见人爱的生存期小符号'_咱们都见过啦。它就负责在API上表明这种依靠联系的对应。

咱们来实践用用这个生存期符号,规范库里的迭代器特质咱们都见过,它长这样:


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

挺好的,可是有个小问题。Item类型的值是与Iterator本身的类型(Self)彻底不能有依靠联系的。为什么呢? 因为你从Iterator取一个值这个动作,产生的这个临时范围(也便是上面的’_),是next这个相关函数的泛型参数。 界说的Item是单独的另一个相关类型,怎么可能用到呢?

大多数时分这个不是什么问题,可是对于某些库的API来说,这个就不够用了。 比方假如有一个迭代器,依次递给用户一些临时文件用,用户什么时分封闭都能够。这个时分你用Iterator,没有任何问题。 可是要是每次生成一个临时文件,加载一个什么数据,你用完之后它需要封闭临时文件来删去的那种, 这个迭代器肯定就会希望你能够告知它你用完了。这样它就能够删掉临时文件了, 或许爽性不删去,而是直接复用它的存储空间来存下一个文件,这些都是ok的。

所以这个时分咱们能够用泛型相关类型来设计这个API。


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

完成时你其实就能够让Item的类型是一个带依靠的类型,比方一个借用, 类型体系能够确保你在下次调用next或许移动析构这个迭代器之前,Item已经不再被用户运用了。好耶~

你讲的太接地气了,能不能来点抽象的? 好嘞,从现在起咱们开端不说人话了。先说一下,这儿要说的依然是简化过的,比方咱们会把各种binder和predicate放一边。

首先咱们来建立泛型类型的姓名和具体类型之间的联系。当然便是个映射联系了。


/// 伪代码
fn generic_type_mapping(_: GenericTypeCtor, _: Vec<GenericArg>) -> Type;

比方Vec<bool>中,Vec便是这个泛型类型的姓名也是它的结构器,<bool>是这个泛型参数的列表,就一项。经过了这个映射,得到了一个Vec<bool>

好,然后是特质,啥是特质啊,特质其实也是一个映射。


/// 伪代码
fn trait_mapping(_: Type, _: Trait) -> Option<Vec<AssociateItem>>;

这儿这个Trait能够起到一个谓词的作用,也便是拿它来对某个类型做断定,结论要么是None,表明“不契合这个特质”, 要么是一个Some(items) ,表明“这个类型契合这个特质”,并映射出一串相关条目。


/// 伪代码
enum AssociateItem {
    AssociateType(Name, Type),
    GenericAssociateType(Name, GenericTypeCtor), // 这次新加的
    AssociatedFunction(Name, Func),
    GenericFunction(Name, GenericFunc),
    AssociatedConst(Name, Const),
}

这儿的AssociateItem::GenericAssociateType是当前rust里唯一一处间接地履行generic_type_mapping的当地。 经过给trait_mapping的第一个参数传不同的Type,就能够用相同的Trait获取到不同的GenericTypeCtor, 然后履行generic_type_mapping,从而在Rust的语法结构下达到了让不同的GenericTypeCtor跟指定的Vec<GenericArg>组合的目的!

趁便提一下,GenericTypeCtor这类东西,便是某些文章里边介绍的HKT。经过以上描绘的这套办法,Rust里第一次加入了供用户运用的HKT才能。 尽管只要这一种方式,可是其他运用方式都能够经过这一种方式做出来。总归便是,奇怪的才能增加了!

我和小鸭子学走路 好嘞,作为收尾,咱们来试着用 GAT 仿制一些其他言语的一些结构。


#![feature(generic_associated_types)]
#![allow(incomplete_features)]
trait FunctorFamily {
    type Type<T>;
    fn fmap<T, U, F>(value: Self::Type<T>, f: F) -> Self::Type<U>
    where
        F: FnMut(T) -> U;
}
trait ApplicativeFamily: FunctorFamily {
    fn pure<T>(inner: T) -> Self::Type<T>;
    fn apply<T, U, F>(value: Self::Type<T>, f: Self::Type<F>) -> Self::Type<U>
    where
        F: FnMut(T) -> U;
}
trait MonadFamily: ApplicativeFamily {
    fn bind<T, U, F>(value: Self::Type<T>, f: F) -> Self::Type<U>
    where
        F: FnMut(T) -> Self::Type<U>;
}

然后咱们来给一个“挑选器”完成这些类型:


struct OptionType;
impl FunctorFamily for OptionType {
    type Type<T> = Option<T>;
    fn fmap<T, U, F>(value: Self::Type<T>, f: F) -> Self::Type<U>
    where
        F: FnMut(T) -> U,
    {
        value.map(f)
    }
}
impl ApplicativeFamily for OptionType {
    fn pure<T>(inner: T) -> Self::Type<T> {
        Some(inner)
    }
    fn apply<T, U, F>(value: Self::Type<T>, f: Self::Type<F>) -> Self::Type<U>
    where
        F: FnMut(T) -> U,
    {
        value.zip(f).map(|(v, mut f)| f(v))
    }
}
impl MonadFamily for OptionType {
    fn bind<T, U, F>(value: Self::Type<T>, f: F) -> Self::Type<U>
    where
        F: FnMut(T) -> Self::Type<U>,
    {
        value.and_then(f)
    }
}

好嘞,然后咱们就能够经过OptionType这个“挑选器”来表达、运用Option作为Functor, Applicative, Monad 的性质了。 怎么样,是不是打开了无数的新的可能性?

作者:CrLF0710

链接:rustmagazine.github.io/rust_magazi…