使用结构体组织相关数据
结构体(struct) 是一种自定义数据类型,允许你将多个相关的值打包并命名,组成一个有意义的整体。在 Auto 中,结构体使用 type 关键字而非 Rust 的 struct 关键字来定义,但概念是相同的。如果你熟悉面向对象语言,结构体就像对象的数据属性。
在本章中,我们将比较元组与结构体的异同,演示如何定义和实例化结构体,并讨论如何使用 type 块内的内联定义和 ext 关键字来定义关联函数和方法。
定义和实例化结构体
结构体与元组类似,都可以持有多个相关值。和元组一样,结构体的各个部分可以是不同的类型。与元组不同的是,在结构体中你需要为每个数据片段命名,这样可以清楚地表达各个值的含义。
在 Auto 中定义结构体,我们使用 type 关键字并为类型命名。然后,在花括号内定义字段的名称和类型。注意,Auto 使用空格分隔的类型标注(name Type),而非 Rust 的冒号语法(name: Type)。
与 Rust 的关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| 定义结构体 | type Name { } | struct Name { } |
| 字段类型语法 | name Type | name: Type |
| 创建实例 | Name(val1, val2) | Name { field: val } |
| 可变实例 | var user = Name(...) | let mut user = Name { ... } |
Auto 使用位置构造函数语法(User(true, "name", ...))而非 Rust 的命名字段语法(User { active: true, ... })。参数按照字段声明的顺序进行匹配。
可变实例
要修改字段,使用 var 创建可变实例:
注意,整个实例必须是可变的;Auto 不允许只将某些字段标记为可变,这与 Rust 一致。
一个使用结构体的示例程序
让我们编写一个计算矩形面积的程序,从简单变量开始,逐步重构为使用结构体。
使用独立变量
这段代码的问题在于 width 和 height 是相关的,但函数签名并没有传达这种关系。让我们用类型来重构。
使用类型重构
现在函数签名清楚地表明它计算的是一个 Rectangle 的面积,且 width 和 height 字段有了名称并被关联起来。
使用 .view 借用
如第 4 章所述,我们可以使用 .view 来借用值而不获取所有权:
方法
方法是在类型的上下文中定义的函数。在 Auto 中,方法可以通过两种方式定义:
- 内联方法 — 直接在
type { }块内定义 - 扩展方法 — 在类型定义外部的
ext块中定义
两种方式都会生成 Rust 的 impl 块。
定义内联方法
在 Auto 中,你可以直接在 type 块内定义方法。self 参数是隐式的 — 在方法体内,使用 .field 来访问实例的字段:
关键区别:
| 特性 | Auto | Rust |
|---|---|---|
| 定义方法 | 在 type { } 块内 | 在单独的 impl 块中 |
| self 参数 | 隐式(不需要写) | 显式 &self |
| 访问自身字段 | .field | self.field |
.field 语法是 self.field 的简写。Auto 自动注入 &self 作为第一个参数,并在转译的 Rust 代码中将 .field 转换为 self.field。
带多个参数的方法
你可以在隐式的 self 之后为方法添加额外参数:
使用 ext 定义关联函数
在 Rust 中,关联函数(如 String::from)是在 impl 块中定义的不以 self 作为第一个参数的函数。在 Auto 中,你使用 ext 关键字在类型初始定义之后添加关联函数和方法:
关联函数的关键映射区别:
| 特性 | Auto | Rust |
|---|---|---|
| 定义扩展 | ext TypeName { } | impl TypeName { } |
| 调用关联函数 | TypeName.method() | TypeName::method() |
| 构造函数调用 | TypeName(args) | TypeName { field: val } |
ext 关键字是 Auto 中等价于 Rust impl 的机制,用于向类型添加方法和关联函数。与 impl 不同,ext 可以在代码库的任何位置使用,不仅限于类型定义之后。
多个 ext 块
每个类型可以有多个 ext 块。这对于按功能组织方法非常有用:
类元组类型
Auto 没有 Rust 的"元组结构体"(即未命名字段的结构体)。取而代之,使用带有命名字段的常规类型,这样可以提供更好的可读性:
类型数据的所有权
在 Auto 中,与 Rust 一样,类型可以拥有其数据(使用
String)或借用数据(使用.view)。当类型拥有其数据时,只要整个类型实例有效,数据就是有效的。Auto 的编译器会自动处理生命周期检查,无需显式标注。
总结
Auto 的 type 关键字提供了与 Rust 的 struct 相同的功能,但语法更加简洁:
| 概念 | Auto | Rust |
|---|---|---|
| 定义类型 | type Name { fields } | struct Name { fields } |
| 字段语法 | name Type | name: Type |
| 创建实例 | Name(val1, val2) | Name { field: val } |
| 可变实例 | var x = Name(...) | let mut x = Name { ... } |
| 内联方法 | 在 type { } 块内 | 在 impl 块中 |
| 扩展方法 | ext Name { } | impl Name { } |
| 访问自身字段 | .field | self.field |
| 关联函数 | ext Name { fn new() } | impl Name { fn new() } |
| 调用关联函数 | Name.fn() | Name::fn() |
类型和枚举(将在第 6 章讨论)是构建程序领域中新类型的基石,让你能够充分利用 Auto 的编译时类型检查。
接下来让我们进入第 6 章,了解枚举和 is 关键字的模式匹配。