第五章前言
struct,或者 structure,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,struct 就像对象中的数据属性。在本章中,我们会对比元组与结构体的异同,演示结构体的用法,并讨论如何在结构体上定义方法和关联函数来指定与结构体数据相关的行为。你可以在程序中基于结构体和枚举(enum)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("[email protected]"),
username: String::from("a123456"),
active: true,
sign_in_count: 1,
}
前提是整个实例必须是可变的。
let mut user1 = User {
email: String::from("[email protected]"),
username: String::from("a123456"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
Rust 并不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
当字段名与字段内容一致时,可以使用简写写法,这里和JS一致:
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
active: user1.active,
sign_in_count: user1.sign_in_count,
};
let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
..user1
};
也可以定义与元组(在第三章讨论过)类似的结构体,称为 元组结构体(tuple structs)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
要定义元组结构体,以 struct 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 Color 和 Point 元组结构体的定义和用法:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
注意 black 和 origin 值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。例如,一个获取 Color 类型参数的函数不能接受 Point 作为参数,即便这两个类型都由三个 i32 值组成。在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用
.
后跟索引来访问单独的值,等等。
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体(unit-like structs)**因为它们类似于 ()
,即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。
在示例 5-1 中的 User 结构体的定义中,我们使用了自身拥有所有权的 String 类型而不是 &str 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
如果要使结构体存储被其他对象拥有的数据的引用,需要用上 生命周期(lifetimes),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "[email protected]",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
编译器会抱怨它需要生命周期标识符:
error[E0106]: missing lifetime specifier
-->
|
2 | username: &str,
| ^ expected lifetime parameter
error[E0106]: missing lifetime specifier
-->
|
3 | email: &str,
| ^ expected lifetime parameter
第十章会讲到如何修复这个问题以便在结构体中存储引用,不过现在,我们会使用像 String 这类拥有所有权的类型来替代 &str
这样的引用以修正这个错误。