Skip to content
📖 Auto 版 Rust Book本书目录

泛型、Spec 与生命周期 ​

每种编程语言都有有效处理概念重复的工具。在 Auto 中,其中一种工具是_泛型_(generics):具体类型或其他属性的抽象替代品。我们可以在编译和运行代码时,无需知道泛型具体代表什么,就能表达泛型的行为或它们之间的关系。

函数可以接受某种泛型类型的参数,而不是像 int 或 String 这样的具体类型,就像函数接受未知值来在多个具体值上运行相同的代码一样。事实上,我们在前面的章节中已经使用了泛型:?T(Option)、!T(Result)、List<T> 和 Map<K, V> 都是泛型类型。

在本章中,我们将探索如何定义自己的泛型类型、函数和方法。然后我们将学习 spec(Auto 中等价于 Rust trait 的概念)来定义共享行为。最后,我们将简要讨论生命周期 —— 以及 Auto 的编译器如何自动处理它们,从而消除对显式生命周期标注的需要。

泛型数据类型 ​

在函数定义中 ​

当定义使用泛型的函数时,我们将泛型放在通常指定数据类型的位置。这使得代码更加灵活,同时避免了重复。

在列表中查找最大值的泛型函数

我们读 fn largest<T>(list []T) T 为"函数 largest 对某种类型 T 是泛型的。"该函数接受一个 T 值的列表并返回一个 T。按照惯例,类型参数名称很短,通常只有一个字母,使用 UpperCamelCase —— T("type"的缩写)是默认选择。

语法与 Rust 相同:类型参数放在函数名和参数列表之间的尖括号 <> 中。

在类型定义中 ​

我们可以在类型的字段中定义泛型类型参数:

持有 `T` 类型 `x` 和 `y` 值的 `Point<T>` 类型

因为我们只使用了一个泛型类型 T,x 和 y 必须是相同类型。如果每个字段需要不同的类型,请使用多个类型参数:

具有两个类型参数的 `Point<T, U>`

在方法定义中(ext) ​

我们可以使用 ext 在泛型类型上定义方法:

使用 `ext` 在 `Point<T>` 上实现方法

注意我们在 ext 之后、Point&lt;T&gt; 之前声明了 T,就像 Rust 的 impl&lt;T&gt; Point&lt;T&gt; 一样。这告诉编译器该方法是为 T 可能代表的任何具体类型定义的。

使用泛型的代码性能 ​

使用泛型没有运行时成本。Auto(像 Rust 一样)使用_单态化_(monomorphization):编译器为每个与泛型一起使用的具体类型生成特定的代码。因此,Point&lt;int&gt; 和 Point&lt;f64&gt; 在编译时成为独立的、优化过的类型。使用泛型没有运行时开销。

用 Spec 定义共享行为 ​

spec("specification"的缩写)定义了特定类型具有的、可以与其他类型共享的功能。Spec 是 Auto 中等价于 Rust trait 的概念(在其他语言中也称为_接口_)。

定义 Spec ​

类型的行为由我们可以在该类型上调用的方法组成。Spec 定义将方法签名分组在一起,定义一组行为:

auto
spec Summary {
    fn summarize(self) String
}

这声明了一个名为 Summary 的 spec,带有一个必需方法:summarize,它接受 self 并返回 String。任何实现此 spec 的类型都必须提供自己的 summarize 方法。

在类型上实现 Spec ​

在 Auto 中,使用 as 来实现 spec:

定义 spec 并在两个类型上实现它

与 Rust 的关键区别:

特性AutoRust
定义 spec/traitspec Summary { ... }trait Summary { ... }
在类型上实现type Foo as Summary { ... }impl Summary for Foo { ... }
方法体在单独的 ext 块中在 impl 块内

类型声明上的 as 关键字表示"此类型符合该 spec"。实际的方法实现放在 ext 块中。

使用 Spec 作为参数 ​

你可以将 spec 作为参数类型,以接受任何实现了它的类型:

将 spec 作为函数参数使用

在 Auto 中,fn notify(item Summary) 接受任何类型实现了 Summary spec 的值。这等价于 Rust 的 impl Trait 语法:fn notify(item: impl Summary)。

注意 Auto 的语法更简洁 —— 你不需要 impl 或 Box&lt;dyn Trait&gt; 来传递符合 spec 的值。编译器自动处理调度。

泛型 Spec ​

Spec 可以有自己的类型参数,就像类型一样:

带有类型参数的泛型 spec

泛型 spec Container&lt;T&gt; 定义了一个契约:任何实现它的类型必须为内部类型 T 提供 get 和 set 方法。Box&lt;T&gt; 类型实现了 Container&lt;T&gt;,ext 块提供了方法体。

Spec 与 Trait 速查 ​

AutoRust含义
spec Foo { ... }trait Foo { ... }定义规范
type Bar as Foo { ... }impl Foo for Bar { ... }在类型上实现 spec
fn foo(x SomeSpec)fn foo(x: impl SomeSpec)Spec 作为参数
spec Container&lt;T&gt;trait Container&lt;T&gt;泛型 spec
ext Type&lt;T&gt; { ... }impl&lt;T&gt; Type&lt;T&gt; { ... }泛型方法扩展

生命周期:Auto 中自动处理 ​

在 Rust 中,生命周期(lifetimes)是泛型标注,告诉编译器引用应该有效多长时间。它们是必要的,因为 Rust 需要关于引用之间关系的显式信息。

Auto 采用了不同的方法:生命周期是完全自动的。编译器在内部跟踪引用有效性,确保内存安全,而无需你编写任何生命周期标注。

不需要生命周期标注 ​

在 Rust 中,一个返回两个字符串切片中较长者的函数需要显式的生命周期标注:

rust
// Rust 需要显式生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

在 Auto 中,同样的函数完全不需要标注:

Auto 自动推断生命周期 —— 不需要标注

Auto 的编译器使用所有权和借用规则(.view、.mut、.move)在编译时跟踪引用有效性,消除了在大多数情况下对显式生命周期参数的需要。

这对你意味着什么 ​

  • 函数签名中没有 'a 标注
  • 类型定义中没有生命周期参数
  • ext 块中没有 &lt;'a&gt; 声明
  • 编译器仍然防止悬垂引用和 use-after-free 错误

如果你来自 Rust,可以这样理解:Auto 的编译器激进地应用 Rust 的生命周期省略规则,加上额外的推断,所以你几乎永远不需要显式编写生命周期。

总结 ​

Auto 提供了与 Rust 相同的泛型编程能力,但语法更简洁:

概念AutoRust
泛型函数fn foo&lt;T&gt;(x T)fn foo&lt;T&gt;(x: T)
泛型类型type Point&lt;T&gt; { x T }struct Point&lt;T&gt; { x: T }
多类型参数type Pair&lt;T, U&gt;struct Pair&lt;T, U&gt;
泛型方法ext Point&lt;T&gt; { ... }impl&lt;T&gt; Point&lt;T&gt; { ... }
Spec (trait)spec Foo { ... }trait Foo { ... }
实现 spectype Bar as Foo { ... }impl Foo for Bar { ... }
Spec 作为参数fn foo(x SomeSpec)fn foo(x: impl SomeSpec)
泛型 specspec Storage&lt;T&gt;trait Storage&lt;T&gt;
生命周期自动显式 'a 标注

最显著的简化是 Auto 的自动生命周期推断。在 Rust 需要在函数签名和类型定义上写 &lt;'a&gt; 标注的地方,Auto 的编译器在内部处理这些,让你专注于逻辑而非生命周期管理。

在下一章中,我们将讨论自动化测试。

基于 MIT 许可发布。