1. 定义结构体(Struct)
定义一个结构体需要使用 struct
关键字,然后给该结构体取一个有意义的名字,接着在花括号内列出字段名称和字段类型。例如,下面的 User
结构体包含四个字段,用来表示用户的相关信息:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
需要注意的是,结构体字段之间用逗号分隔,字段类型可以各不相同。字段名有助于我们理解各数据的含义,相比于元组,这种方式更具可读性和可维护性。
2. 实例化结构体
定义完结构体后,我们就可以基于它创建具体的实例。创建实例需要在结构体名称后面的花括号中填写 字段: 值
。例如:
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
上述代码创建了一个名为 user1
的 User
实例。
- 访问字段时,使用点号(
.
)语法,比如user1.email
。 - 如果想要对结构体的字段进行修改,需要将整个实例声明为可变(
mut
),如下所示:
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
// 修改 email
user1.email = String::from("[email protected]");
Rust 不允许只将部分字段设为可变;只有把整个实例设为 mut
,才能修改任意字段的值。
3. 返回结构体实例的函数
我们可以编写一个函数来返回一个结构体实例。以下例子中,build_user
函数接收用户名和邮箱,返回一个 User
实例:
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
这其中有一些重复:结构体字段和函数参数名字完全相同时,写起来显得冗余。Rust 提供了字段初始化简写语法(field init shorthand) 来简化这种情况:
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
当字段名和变量名相同时,可以直接省略 username: username
,只写 username
。
4. 从已有实例更新字段:结构体更新语法
有时我们想根据现有的结构体实例创建一个新实例,只修改其中一些字段,而其余字段沿用旧实例的值。使用“结构体更新语法”(struct update syntax)能让我们少写大量重复代码。先看常规写法:
// 常规写法:手动为所有字段赋值
let user2 = User {
active: user1.active,
username: String::from("new_user"),
email: String::from("[email protected]"),
sign_in_count: user1.sign_in_count,
};
使用更新语法,只需要在花括号最后使用 ..
加上已存在的实例名称:
let user2 = User {
email: String::from("[email protected]"),
// 其他未指定的字段全部拷贝自 user1
..user1
};
这样就将 active
、username
和 sign_in_count
字段从 user1
中拷贝了过来,而 email
则使用了新的值。
需要注意的是,这种用法会发生所有权转移(move)。如果被转移的字段(如 String
)在新实例 user2
中使用了 user1
的原始值,那么 user1
中对应的字段不再有效。如果被转移的字段类型实现了 Copy
trait(如 u64
),则不会失效,因为它是按值拷贝的。
5. 元组结构体(Tuple Struct)
Rust 还支持一种“元组结构体”(tuple struct),其形式与常规结构体类似,只是不为字段命名,而是直接在结构体名后面写上字段类型,如同元组一样。例如:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("Color: {}, {}, {}", black.0, black.1, black.2);
println!("Point: {}, {}, {}", origin.0, origin.1, origin.2);
}
black
和origin
虽然都含有三个i32
,但它们属于不同的类型:Color
和Point
。- 这种设计可以让你在概念上区分“颜色”和“坐标点”,即使它们的内部结构相同。
6. 类单元结构体(Unit-Like Struct)
Rust 允许定义没有任何字段的结构体,被称为类单元结构体(unit-like struct)。它的用法和 ()
(unit 类型)非常类似。比如:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
这里的 AlwaysEqual
并没有任何字段,但是将来可以为它实现某些特定的 trait,使得其任意两个实例都“总是相等”(或者做其他自定义的行为)。这种结构体常用于在类型系统层面上代表某种独特概念,或者在泛型、trait 中提供占位符作用。
7. 结构体中的所有权问题
在上面的 User
例子里,我们使用了 String
而不是 &str
作为字段类型。这是因为我们希望这个结构体拥有自己的数据,并且在结构体生命周期内字段数据一直有效。如果结构体内部要存储引用(如 &str
),则需要用到生命周期(lifetime) 来保证被引用的数据在结构体作用范围内不被释放。
如果你试图直接在结构体中写:
struct User {
active: bool,
username: &str, // 会出错
email: &str, // 会出错
sign_in_count: u64,
}
编译器会提示需要生命周期标注(lifetime specifier),否则无法保证引用的有效性。关于如何在结构体中使用引用,以及如何正确指定生命周期,我们会在更高级的章节(通常是第 10 章)做更深入的讨论。