C5 使用 Struct
Rust 中 struct 为用于定义组合数据的自定义数据类型, 和面向对象语言中的 class
类似:
- struct 可以有关联函数, 类似类方法(class method).
- struct 可以有以
self
(或mut self
/&self
/&mut self
) 为第一个参数的函数, 称为方法(method). - struct 可以实现 trait, 类似实现接口.
- 但 struct 无法继承另外一个 struct, 只能包含另外一个 struct 作为成员.
- struct 和 enum 均有访问控制(pub, 否则默认 private)
此外, 在 rust 中还有 enum, 会在下一章详细看.
定义和初始化 struct
和 tuple 类似, struct 也用于组织不同类型的数据, 但和 tuple 不同的是, struct 需要为每个成员命名, 如下所示:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
上述定义了一个名为 User
的 struct, 其中有四个成员(field), 且每个成员类型单独指定.
定义 struct 后, 要使用 struct, 需要首先进行初始化:
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
初始化的默认语法即 struct 后跟随大括号并对每个成员进行初始化, 在没有实现 Default
的情况下(无法使用 ::default()
), struct 初始化时必须对每个成员进行初始化(即赋值).
使用 .
访问 struct 的成员, 如: user1.active
.
struct 初始化时, 若遇到和成员同名的变量, 可以进行简化形式的赋值:
fn build_user(email: String, username: String) -> User {
User {
active: true,
username, // 直接使用变量名
email, // 同上
sign_in_count: 1,
}
}
如果从同一类型的 struct 初始化新的 struct, 可以通过 ..
将现有 struct 成员展开到新建的 struct:
let user2 = User {
email: String::from("[email protected]"), // 不同的成员单独赋值
..user1 // 将之前的 user1 展开到新建的 struct 中
};
不过, 当使用展开语法的时候需要注意: 现有 struct 的成员被展开到新 struct, 实际是成员的值被 move 到新 struct(对于非 Copy
成员而言). 结果就是这些被 move 的成员将无法再次通过现有 struct 访问:
let u2 = User {
email: "[email protected]".to_string(),
..u1
};
// 如下三个成员在后续访问时:
u1.email; // Ok 可以在 u1 上访问, 因为 email 没有被 move, u2 中 email 是新建的.
u1.user_name; // Error! 无法在 u1 上访问, 因为 user_name 被 move 到了 u2 中.
u1.sign_in_count; // Ok 是 Copy 类型的, 不存在 move 可能性.
特殊 struct 形式
有如下几种:
-
Tuple Struct
: 即不对成员命名的 struct, 比如:struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// ...
// Color 和 Point 是不同类型的 struct, 即便它们的成员一致. 这点和 tuple 不一样.
let c = Color(10, 255, 0);
let p = Point(1, 2, 1);
// 需要使用 `.0`, `.1` 类型下标语法访问其成员
c.0 // 10
c.1 // 255Tuple Struct 除了上述用途外, 还可以在 FFI 场景下对不同的 C 透明指针(
void*
)进行区分表示, 让代码更易读. -
Unit Struct
: 即没有任何成员的 struct, 比如:struct AlwaysEqual; // Unit Struct
let s = AlwaysEqual; // Unit Struct 的初始化其用途主要是: 当想实现一个 trait, 但不需要包含任何 struct 成员时.
Struct 成员的 Ownership
上面已经看到了 struct 成员是可以被 move 出 struct 的, 当 move 出去后就无法再在原 struct 上访问了.
struct 成员可以是任何类型的, 当然也就可以是引用类型的, 当 struct 成员是引用类型时, 需要为其成员指定生命期标记. 作用是为了保证引用成员在访问时一直是合法的(至少引用成员引用的内容应和 struct 存在一样久). 关于 Lifetime 的详细讲解会放到 C10.
使用 Derived Trait 为 Struct 添加功能
可以使用 outer attribute 中的 #[derive()]
为 struct 添加 trait 的默认实现, 比如:
#[derive(Debug)] // 获得 Debug 默认实现.
struct Color {
red: i32,
green: i32,
blue: i32
}
关于 Derivable Traits, Rust 提供了许多, 详见这个列表
一些小细节
println!
宏中的一些格式化控制:
{}
: 使用Display
trait 的输出作为打印内容{:?}
: 使用Debug
trait 的输出作为打印内容{:#?}
: 使用更易读的Debug
输出作为打印内容(pretty Debug)
其他:
println!
的输出为 stdout, 传入被输出内容的引用.dbg!
的输出为 stderr, 会获取被输出内容的 Ownership, 完成后再归还.
实现 struct 方法
实现 struct 方法的语法如下:
struct Color {
// ...
}
impl Color { // 关联函数及method 实现
// 关联函数
fn new_color() -> Self {
//...
}
// method
fn is_dark(&self) -> bool {
//...
}
}
impl Debug for Color { // 实现一个 trait
// ...
}
其中:
&self
实际是self: &Self
的简写, 而Self
表示的是这个 impl 所实现的类型, 这里就是 Color.&self
为了 borrow self, 而不会将 self move 进来. 要 move self 进来, 就直接使用self
作为第一个参数. (move self 的方法一般是用于要做某种变换的时候, 场景不多).- 同理, 要改变 self, 可以使用
mut self
或&mut self
- 方法调用时, 一般不需要写
(&c).is_dark()
, 因为 rust 会自动根据方法第一个参数的类型进行补全(&
,&mut
,*
等等). - 关联函数调用使用, 使用
::
符进行.