struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。
定义结构体,需要使用 struct
关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 字段(field)。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {}
相比较元组而言,元组自身并没有名字,每个数据也没有名字,只能按顺序来指定或访问实例中的值。元组只是多个值的集合,而结构体和枚举可以拥有实现(方法)。Rust没有类这个概念,枚举和结构体是Rust面向对象的实体基础。
结构体默认是不可变的,我们通过声明let mut来声明一个可变结构体的变量。
当参数名与字段名都完全相同,我们可以使用 字段初始化简写语法,这个类似JavaScript ES6里的写法。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("[email protected]"),
String::from("someusername123"),
);
}
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有用的。这可以通过 结构体更新语法(struct update syntax)实现:
fn main() {
// --snip--
let user2 = User {
email: String::from("[email protected]"),
..user1
};
}
虽然JavaScript中也有类似的展开,但是很不幸如果按照上述写法,user2的值将和user1完全一样,因为email会被user1的展开所覆盖,如果想要类似的效果,需要把email字段写到user1的展开后面。
也可以定义与元组(在第三章讨论过)类似的结构体,称为 元组结构体(tuple structs)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的。元组结构体实例类似于元组,你可以将它们解构为单独的部分,也可以使用 .
后跟索引来访问单独的值。
我们也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体(unit-like structs)因为它们类似于 ()
,即“元组类型”一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。
方法(method)与函数类似:它们使用 fn
关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文),并且它们第一个参数总是 self
,它代表调用该方法的结构体实例。
为了使函数定义于 Rectangle
的上下文中,我们开始了一个 impl
块(impl
是 implementation 的缩写),这个 impl
块中的所有内容都将与 Rectangle
类型相关联。接着将 area
函数移动到 impl
大括号中,并将签名中的第一个(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 self
。
所有在 impl
块中定义的函数被称为 关联函数(associated functions),因为它们与 impl
后面命名的类型相关。我们可以定义不以 self
为第一参数的关联函数(因此不是方法),因为它们并不作用于一个结构体的实例。不是方法的关联函数经常被用作返回一个结构体新实例的构造函数:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}
关键字 Self
在函数的返回类型中代指在 impl
关键字后出现的类型,在这里是 Rectangle
使用结构体名和 ::
语法来调用这个关联函数:比如 let sq = Rectangle::square(3);
。这个函数位于结构体的命名空间中:::
语法用于关联函数和模块创建的命名空间。
每个结构体都允许拥有多个 impl
块。
枚举类似结构体,也可以作为值的集合。但是从语义上讲,结构体的字段组成了这个结构体描述的实体,而枚举是多个选项的集合,每个选项是一种实体,而枚举值只能是其中的一个选项。
枚举使用enum声明枚举类型,枚举类型的可能选项是枚举的成员值。例如:
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(IpAddrKind::V4);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {}
枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。
枚举值可以和值进行关联,这个和Swift是类似的。
fn main() {
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
上面的例子里,我们将IP枚举值和具体的IP值进行了关联。甚至于说,不同的枚举值可以和不同类型、不同数量的值进行关联:
fn main() {
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
结构体和枚举还有另一个相似点:就像可以使用 impl
来为结构体定义方法那样,也可以在枚举上定义方法。
match控制流
针对枚举值,在其他语言里我们常常使用类似Swift里的switch、case,以及kotlin里的where来做类似的匹配和处理。Rust里使用的是match。
绑定值的模式
因为枚举可以关联值,所以也可以在模式匹配的时候,绑定关联的值:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
value_in_cents(Coin::Quarter(UsState::Alaska));
}
match匹配必须覆盖全部的情况,否则编译器会报错。对于不想处理的情况,可以使用other作为通配模式。如果我们甚至都不想使用这个通配的值,我们可以用“_”作为other的代替来占位。
我们常常需要从Option
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}
这种情况,我们可以使用if let来简化:
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
}
当然也不仅限于Option
参考:
1.使用结构体组织相关联的数据
2.枚举和模式匹配