Hello Rust [3] - 结构与枚举
和元组类似,结构体也是由不同类型的值组成,不同在于需要命名各个部分的值。(类比对象)
// 元组
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 定义结构体
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// 使用结构体
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// 单独修改借口替的值
user1.email = String::from("anotheremail@example.com");
}
使用函数来构造一个结构体的实例,可以直接返回:
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
// 或者省略
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
解构语法:
fn main() {
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
注意,解构语法其实是移动了数据,不是复制,这里赋值以后,就不能再使用**user1**了
其他结构体
元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
元组结构体就是命名的元组,可以用来重复构造相同类型的元组。
类单元结构体
常用于在某个类型上实现trait但是不需要再类型中存储数据的时候。
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
结构体和所有权
我们在命名结构体的时候使用了String而不是&str,就是想让结构体拥有自己的所有权。
如果想要结构体存储其他对象拥有数据的引用,则需要考虑生命周期(lifetimes),来确保结构体引用的数据有效性和结构体本身来保持一致。
直接使用引用的结构体代码是无效的:
struct User {
active: bool,
username: &str,
email: &str,
sign_in_count: u64,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
方法结构体
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl是implementation的缩写,块中的所有内容都将与Rectangle类型关联。- 方法中可以通过
&self来调用自身。注意这里仍然是一个引用。 - 方法和字段可以是同一个名称, 例如:
impl Rectangle {
fn width(&self) -> bool {
self.width > 0
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
if rect1.width() {
printlÏn!("The rectangle has a nonzero width; it is {}", rect1.width);
}
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
- 方法的第一个参数是自身对象,剩下的参数则会被传进方法。
关联函数
在impl中定义的函数 叫做 关联函数 。 因为他们和impl后面命名的类型是相关的。
如果参数的第一个参数不是&self,那么他们就不是方法。
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
Rectangle
可以使用::语法来调用这个关联函数。impl是可以分开定义的,比如这样:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
除了使用结构体来创建自定义类型还可以使用枚举和模式。
枚举
// 定义两个不同类型的IP地址
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) {}
- 当然可以把数据直接定义在我们的结构体上:
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"));
}
同一个枚举中可以处理各种类型,比如下面这个例子:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
我们也可以给枚举定义方法:
fn main() {
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
}
Option
在有空值的语言中, 变量有两种状态:空值和非空值。
Rust没有空值,但是可以表示一个编码存在或者不存在的概念的枚举。这个枚举是Option<T>。
其中T是
enum Option<T> {
None,
Some(T),
}
这个枚举非常有用。所以它被包含在prelude中,不需要显示引入,他的成员也可以,直接像下面这样调用:
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
}
当有一个Some的时候,我们就知道存在一个值,而这个值保存在Some中。当有个None值时,在某种意义上,它和空值有相同的意义:并没有一个有效的值。
比空值更好的意义是:Option<T>是限定了类型的。
枚举和match
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
printIn!("Lucky penny!")
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
match 和 option
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
// 不存在值的时候,返回None
None => None,
// 当存在只的时候,给加1
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
通配符和 _占位符
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
// 统配模式
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
}
不用这种统配模式的话,可以使用_, 这是一个特殊的模式,可以匹配任意值而不绑定到该值。这告诉Rust我们不会使用这个值:
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
}
if let 简洁控制流
if let模式可以来处理只匹配一个模式的值而忽略其他模式的情况。
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
// => 可以用更简洁的方式书写
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
}
if let语法获取通过等号分隔的一个模式和一个表达式,它的工作方式和match是相同的,这里的表达式对应的就是match,而模式对应第一个分支。
另一个方面,这种模式会失去穷尽性检查。