目录

第七章 结构体与枚举

7.1 结构体(Struct)

结构体是Rust中创建自定义类型的方式,允许你将多个相关的值组合在一起。

定义结构体

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

创建实例

let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

访问字段

println!("用户名:{}", user1.username);
println!("邮箱:{}", user1.email);

可变实例

let mut 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");

注意: Rust不允许只将某个字段标记为可变,整个实例必须是可变的。

字段初始化简写

当变量名与字段名相同时,可以简写:

fn build_user(email: String, username: String) -> User {
    User {
        email,      // 等价于 email: email
        username,   // 等价于 username: username
        active: true,
        sign_in_count: 1,
    }
}

结构体更新语法

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1  // 其余字段从user1获取
};

注意: 使用..语法会发生移动,如果user1中有被移动的值,user1将失效。

7.2 元组结构体

没有命名字段的结构体,称为元组结构体:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
 
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

访问:

println!("R: {}, G: {}, B: {}", black.0, black.1, black.2);

用途:

7.3 单元结构体

没有任何字段的结构体:

struct AlwaysEqual;
 
let subject = AlwaysEqual;

用途: 用于实现trait而不需要存储数据。

7.4 结构体的方法

定义方法

struct Rectangle {
    width: u32,
    height: u32,
}
 
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
 
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}
 
fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("面积是{}平方像素", rect1.area());
}

&self参数:

关联函数

不带self参数的函数,通常用于构造函数:

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}
 
// 使用::调用
let sq = Rectangle::square(3);

多个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
    }
}

7.5 枚举(Enum)

枚举允许你定义一个类型,它的值可以是几个可能的变体之一。

定义枚举

enum IpAddrKind {
    V4,
    V6,
}
 
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

带数据的枚举

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),
}

在枚举上定义方法

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to ({}, {})", x, y),
            Message::Write(text) => println!("Write: {}", text),
            Message::ChangeColor(r, g, b) => println!("Color: R{}, G{}, B{}", r, g, b),
        }
    }
}
 
let m = Message::Write(String::from("hello"));
m.call();

7.6 Option枚举

Option是标准库中定义的最常用的枚举:

enum Option<T> {
    Some(T),
    None,
}

使用Option:

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

Option与null的区别:

let x: i8 = 5;
let y: Option<i8> = Some(5);
 
// let sum = x + y;  // 错误!不能相加

处理Option:

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}
 
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

常用方法:

let x: Option<i32> = Some(5);
 
// unwrap(危险)
let val = x.unwrap();  // 如果是None会panic
 
// unwrap_or(安全)
let val = x.unwrap_or(0);  // None时返回默认值
 
// unwrap_or_else
let val = x.unwrap_or_else(|| 0);
 
// map
let y = x.map(|v| v * 2);  // Some(10)
 
// is_some/is_none
if x.is_some() {
    println!("有值");
}
 
// if let
if let Some(v) = x {
    println!("值是{}", v);
}

7.7 Result枚举

Result用于可能失败的操作:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

使用Result:

use std::fs::File;
 
let f = File::open("hello.txt");
 
let f = match f {
    Ok(file) => file,
    Err(error) => {
        panic!("打开文件失败:{:?}", error);
    }
};

匹配不同错误:

use std::fs::File;
use std::io::ErrorKind;
 
let f = File::open("hello.txt");
 
let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => panic!("创建文件失败:{:?}", e),
        },
        other_error => panic!("打开文件失败:{:?}", other_error),
    },
};

快捷方法:

// unwrap(危险)
let f = File::open("hello.txt").unwrap();
 
// expect(带自定义消息)
let f = File::open("hello.txt")
    .expect("hello.txt应该存在");
 
// unwrap_or_else
let f = File::open("hello.txt").unwrap_or_else(|error| {
    if error.kind() == ErrorKind::NotFound {
        File::create("hello.txt").unwrap_or_else(|error| {
            panic!("创建文件失败:{:?}", error);
        })
    } else {
        panic!("打开文件失败:{:?}", error);
    }
});

7.8 if let和while let

if let简化模式匹配

// match写法
let some_value = Some(3);
match some_value {
    Some(3) => println!("是三"),
    _ => (),
}
 
// if let写法
if let Some(3) = some_value {
    println!("是三");
}

带else:

if let Some(3) = some_value {
    println!("是三");
} else {
    println!("不是三");
}

while let

let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
 
// 弹出所有元素
while let Some(top) = stack.pop() {
    println!("{}", top);
}

练习题

练习题7.1:实现矩形结构体

struct Rectangle {
    width: u32,
    height: u32,
}
 
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
 
    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }
 
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width >= other.width && self.height >= other.height
    }
 
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}
 
fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };
 
    println!("rect1的面积:{}", rect1.area());
    println!("rect1的周长:{}", rect1.perimeter());
    println!("rect1能容纳rect2:{}", rect1.can_hold(&rect2));
    println!("rect1能容纳rect3:{}", rect1.can_hold(&rect3));
 
    let sq = Rectangle::square(10);
    println!("正方形面积:{}", sq.area());
}

练习题7.2:实现链表节点

enum List {
    Cons(i32, Box<List>),
    Nil,
}
 
use List::{Cons, Nil};
 
fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
 
    // 遍历链表
    fn print_list(list: &List) {
        match list {
            Cons(val, next) => {
                println!("{}", val);
                print_list(next);
            }
            Nil => println!("结束"),
        }
    }
 
    print_list(&list);
}

练习题7.3:实现扑克牌枚举

#[derive(Debug)]
enum Suit {
    Hearts,
    Diamonds,
    Clubs,
    Spades,
}
 
#[derive(Debug)]
enum Rank {
    Ace,
    Number(u8),
    Jack,
    Queen,
    King,
}
 
#[derive(Debug)]
struct Card {
    suit: Suit,
    rank: Rank,
}
 
impl Card {
    fn value(&self) -> u8 {
        match &self.rank {
            Rank::Ace => 11,
            Rank::Number(n) => *n,
            Rank::Jack | Rank::Queen | Rank::King => 10,
        }
    }
}
 
fn main() {
    let card = Card {
        suit: Suit::Hearts,
        rank: Rank::Ace,
    };
 
    println!("{:?}", card);
    println!("牌面值:{}", card.value());
}

练习题7.4:实现安全除法

fn safe_divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}
 
fn main() {
    match safe_divide(10.0, 2.0) {
        Some(result) => println!("结果:{}", result),
        None => println!("除数不能为零"),
    }
 
    if let Some(result) = safe_divide(10.0, 0.0) {
        println!("结果:{}", result);
    } else {
        println!("除数不能为零");
    }
}

本章小结

本章学习了Rust的结构体和枚举:

结构体和枚举是Rust类型系统的核心,掌握它们对于编写Rust程序至关重要。