Rust基础[part7]_枚举、结构体
枚举
枚举用于表示一个值可能是多种变体(Variant)中的一种。每个变体可以有不同的数据类型:
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 带命名数据(类似结构体)
Write(String), // 带单个值(类似元组)
ChangeColor(i32, i32, i32), // 带多个值(类似元组)
}
基本使用
enum Pets {
Cat(String),
Dog { name: String, age: usize },
}
fn print_pet_info() {
let a = Pets::Cat("Whiskers".to_string());
let b = Pets::Dog {
name: "Buddy".to_string(),
age: 5,
};
// Debug trait allows us to print the enum variants
println!("cat is {:?}", a);
println!("dog is {:?}", b);
//impl是用来为枚举或结构体定义方法的
impl Pets {
fn speak(&self) {
println!("hi");
}
}
a.speak();
// 关联函数是与类型相关的函数,可以通过类型名直接调用
// 关联函数不需要实例化对象就可以调用
impl Pets {
fn log(name: String) {
println!("Logging pet: {}", name);
}
}
Pets::log("Whiskers".to_string());
}
语法&规范
语法
- field-less enum
- unit-only enum
规范
Pascal Case
snake_case
用法
枚举通常与模式匹配(match
)结合使用,以处理不同的变体:
match & if let
// match 语句:
// 用于模式匹配,可以根据枚举的不同变体执行不同的代码
match a {
Pets::Cat(_) => {
println!("This is a cat");
}
Pets::Bird => {
println!("This is a bird");
}
Pets::Dog { name, age } => {
println!("This is a dog named {} and age {}", name, age);
}
// 这里的 _ 是一个通配符,表示匹配所有未被前面分支匹配的情况
_ => {
println!("This is not a cat");
}
}
// if let 语句:
// 用于简化模式匹配,当只关心某个特定变体时,可以使用 if let
// 这种方式可以避免使用 match 的冗长语法
if let Pets::Dog { name, age } = &b {
println!("This is a dog named {} and age {}", name, age);
} else {
println!("This is not a dog");
}
Option
Option 的作用是安全地表达“有值”或“没有值”的情况,避免空指针(null)带来的运行时错误。在 Rust 里没有 null,所有“可能为空”的场景都用 Option 来表达。
- 你写的函数有时可能找不到结果、读取不到数据、或者参数可能为空,这时用 Option 返回 Some(值) 或 None。
- 这样编译器会强制你检查“有没有值”,防止忘记判断导致程序崩溃。
fn option_example() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
// 使用 match 处理 Option
match some_value {
Some(val) => println!("Got a value: {}", val), // Some 分支
None => println!("No value found"), // None 分支
}
if let Some(val) = none_value {
println!("Got a value: {}", val);
} else {
println!("No value found");
}
}
Result
result 是一个枚举类型,用于表示操作的结果,Result<T, E> 有两个变体:Ok(T) 和 Err(E);Ok(T) 表示操作成功,并包含一个值 T,Err(E) 表示操作失败,并包含一个错误值 E;Result<T, E> 常用于处理可能失败的操作,例如文件读取、网络请求等
- 可以提高代码的安全性和可读性
- 可以避免异常处理(exception handling)的复杂性
fn result_example() {
let ok_value: Result<i32, &str> = Ok(42);
let err_value: Result<i32, &str> = Err("Error occurred");
match ok_value {
Ok(val) => println!("Got a value: {}", val), // Ok 分支
Err(err) => println!("Error: {}", err), // Err 分支
}
if let Err(err) = err_value {
println!("Error: {}", err);
}
}
Option & Result 转换
option-> result: ok_or()
fn option_result_convert() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
// Option 转 Result
let result_from_some: Result<i32, &str> = some_value.ok_or("No value found");
let result_from_none: Result<i32, &str> = none_value.ok_or("No value found");
match result_from_some {
Ok(val) => println!("Got a value: {}", val),
Err(err) => println!("Error: {}", err),
}
match result_from_none {
Ok(val) => println!("Got a value: {}", val),
Err(err) => println!("Error: {}", err),
}
}
result-> option: ok()
fn option_result_convert() {
// Result 转 Option
let ok_value: Result<i32, &str> = Ok(42);
let err_value: Result<i32, &str> = Err("Error occurred");
let option_from_ok: Option<i32> = ok_value.ok();
let option_from_err: Option<i32> = err_value.ok();
match option_from_ok {
Some(val) => println!("Got a value: {}", val),
None => println!("No value found"),
}
match option_from_err {
Some(val) => println!("Got a value: {}", val),
None => println!("No value found"),
}
}
常见api示例
fn enum_api_example() {
let option1 = Some(5);
let option2: Option<i32> = None;
let option_mapped = option1.map(|x: i32| x * 2); // 使用 map 方法对 Some 值进行操作
println!("Option1 after map: {:?}", option_mapped); // 对 Some 值进行操作
let option2_mapped = option2.map(|x: i32| x * 2); // 对 None 值进行操作时不会执行闭包
println!("Option2 after map: {:?}", option2_mapped); // 对 None 值进行操作
option1.and_then(|x: i32| Some(x * 2)); // and_then 方法用于链式调用
option2.and_then(|x: i32| Some(x * 2)); // 对 None 值进行操作时不会执行闭包
let option1_or_else = option1.or_else(|| Some(0)); // or_else 方法用于提供默认值
println!("Option1 or else: {:?}", option1_or_else); // 对 Some 值提供默认值
let option2_or_else = option2.or_else(|| Some(10)); // 对 None 值提供默认值
println!("Option2 or else: {:?}", option2_or_else); // 对 None 值提供默认值
option1.unwrap_or(0); // unwrap_or 方法用于获取 Some 值或提供默认值
option2.unwrap_or(0); // 对 None 值提供默认值
option1.is_some(); // 检查是否为 Some 值
option2.is_some(); // 检查是否为 Some 值
option1.is_none(); // 检查是否为 None 值
let result: Result<i32, i32> = Ok(1);
let error: Result<(), &str> = Err("An error occurred");
result.map(|_| println!("Success!")); // 使用 map 方法对 Ok 值进行操作
error.map(|_| println!("Success!")); // 对 Err 值进行操作时
// 不会执行闭包
result.and_then(|val| Ok(val)); // and_then 方法用于链式调用 返回的是result类型
error.and_then(|val| Ok(val)); // 对 Err 值进行操作时不会执行闭包
// ⚠️ or_else只会在 Err 分支执行
result.or_else(|val: i32| Err(val)); // or_else 方法用于提供默认值
error.or_else(|_| Err(())); // 对 Err 值提供默认值
result.unwrap_or_else(|val: i32| 1); // unwrap_or_else 返回的是最终的值
error.unwrap_or_else(|val| {
println!("Error occurred: {}", val);
()
}); // 对 Err 值提供默认值
result.is_ok(); // 检查是否为 Ok 值
error.is_ok(); // 检查是否为 Ok 值
result.is_err(); // 检查是否为 Err 值
error.is_err(); // 检查是否为 Err 值
}
⚠️ 坑
unwrap_or_else
得到的是最终的值(T)。or_else
得到的是另一个 Option/Result(Option 或 Result<T, E>)。or_else
只会在Err
的时候调用闭包,而闭包参数val
的类型应该是E
(也就是Err
的类型),而不是Ok
的类型
练习:
分析enum的内存大小
#[test]
fn enum_size_analysis() {
use std::mem::size_of;
enum MyEnum {
A(u8, u8),
B,
C {},
}
enum EnumA1 {
A = 255,
}
enum EnumA2 {
A = 255,
B,
}
println!("MyEnum size: {} bytes", size_of::<MyEnum>());
println!("EnumA1 size: {} bytes", size_of::<EnumA1>());
println!("EnumA2 size: {} bytes", size_of::<EnumA2>());
}
结构体
创建
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
pub fn struct_example() {
// 创建结构体实例
// 1.每个字段都需要实例化
// 2.字段顺序可以任意
// 3.字段名逗号分隔
let username = String::from("someusername123");
let user1 = User {
active: true,
username, // 简写
email: String::from("someone@example.com"),
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1 // 结构体更新语法,没有逗号
};
}
常见使用
// 1. 访问字段
println!("The username is {}", user1.email);
//2 修改结构体字段(可变性)
// 整个实例必须是可变的
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
元祖结构体(tuple struct)
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
//3 使用元组结构体
fn tuple_strut() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
println!("{}", black.0);
}
单元结构体(unit-like struct)
// 单元结构体
fn unit_struct() {
// 单元结构体创建
let always_equal = AlwaysEqual;
// 我们不关心AlwaysEqual实例中的任何内容,我们只关心其类型,然后可以为它实现某个特征
impl SomeTrait for AlwaysEqual {
// 函数体省略
}
}
//单元结构体
struct AlwaysEqual;
所有权
{//3 所有权
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
let email = user1.email;
println!("{}", user1.email);// 所有权转移
print_user(user1);// 这里也会报错
}
fn print_user(user: User) {
println!("{:?}", user);
}
--------------------------------------------------
error[E0382]: borrow of moved value: `user1.email`
--> src/struct_example.rs:44:20
|
43 | let email = user1.email;
| ----------- value moved here
44 | println!("{}", user1.email);// 所有权转移
| ^^^^^^^^^^^ value borrowed here after move
- 一旦结构体中某一个字段发生了所有权移动,那么整个结构体都会不能再赋值了
- 使用没有实现copy特性的类型,都会发生所有权的转移。
定义方法
/**
* 结构体方法的使用
*/
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
// new 方法可以创建一个 Self 的实例
Self { width, height }
}
fn set_width(&mut self, width: u32) {
self.width = width;
}
fn area(&self) -> u32 {
self.width * self.height
}
fn is_wide(&self) -> bool {
self.width > self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[test]
fn struct_function_example() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
let mut rect2 = Rectangle {
width: 30,
height: 50,
};
rect2.set_width(20);
println!(
"The area of the rectangle is {} square pixels.",
rect2.area()
);
if rect2.is_wide() {
println!("The rectangle is wide.");
} else {
println!("The rectangle is not wide.");
}
// 使用new
let rect3 = Rectangle::new(30, 50);
// can_hold
println!(
"The area of the rectangle is {} square pixels.",
Rectangle::area(&rect3)
);
}
实现trait
实现了通用的计算逻辑
trait Shape {
fn area(&self) -> u32;
}
impl Shape for Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
trait Shape {
fn area(&self) -> u32;
}
impl Shape for Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Shape for Circle {
fn area(&self) -> u32 {
3 * self.radius * self.radius
}
}
// 新建一个圆形
struct Circle {
radius: u32,
}
impl Circle {
fn new(radius: u32) -> Self {
Circle { radius }
}
}
/**
* 通过impl Trait可以创建一个函数,
* 这个函数的参数是任何实现了Shape trait的类型。
* 这个函数可以传入圆形和矩形,都可以进行area计算,实现了通用的计算逻辑
*/
fn print_area(shape: &impl Shape) {
println!("{}", shape.area());
}
#[test]
fn test_trait_impl() {
// 使用trait通用计算
let rect4 = Rectangle::new(30, 50);
let circle1 = Circle::new(100);
print_area(&rect4);
print_area(&circle1);
}
自己实现Display
如果我们想打印出来整个矩形,该如何处理呢
use std::fmt::Display;
impl Display for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for _ in 0..self.height {
let mut s = String::new();
for w in 0..self.width {
s.push('6');
}
write!(f, "{}\n", s);
}
Ok(())
}
}
#[test]
fn print_the_rectangle() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("{}", rect1);
}
成功:
自己实现debug
一般可以通过派生来实现debug
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
就可以使用"{:?}"
打印结构体
#[test]
fn print_the_rectangle() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("{:?}", rect1);
}
----------------------------
Rectangle { width: 30, height: 50 }
不使用派生,自己实现:
use std::fmt::Debug;
impl Debug for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Rectangle: width: {}, height: {}",
self.width, self.height
);
Ok(())
}
}