跳到主要内容

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
}
}
  • implimplementation的缩写,块中的所有内容都将与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,而模式对应第一个分支。
另一个方面,这种模式会失去穷尽性检查。