i8, i16, i32, i64u8, u16, u32, u64- let i = 0; // 类型推断
-
- let n: i32 = 1; // 显式类型声明
- let mut n =0;
- n = n + 1;

注意,let s: str = "Hello world"; 是不正确的,因为 str 类型不能单独使用。它必须通过引用(&str)来使用。
- let mut v: Vec<i32> = Vec::new();
- v.push(1);
- v.push(0);
在 Rust 中,所有变量在使用之前必须初始化。这是为了防止未初始化变量引起的未定义行为。因此,您不能声明一个未初始化的数组或变量。
- // 创建一个可变数组 `arr`,包含4个 `i32` 类型的元素,将所有元素初始化为0
- let mut arr: [i32; 4] = [0; 4];
-
- // 或者,可以逐个初始化每个元素
- let mut arr: [i32; 4] = [0, 0, 0, 0];
-
- // 修改数组的元素
- arr[0] = 0;
- arr[1] = 1;
- arr[2] = 2;
- arr[3] = 3;
- // 使用 for 循环迭代向量中的元素
- for i in v.iter() {
- println!("{}", i); // 打印每个元素
- }
while 循环: - while i < 9 {
- i += 1;
- println!("i = {}", i); // 打印每次递增后的值
- }
- fn main() {
- let mut i = 0; // 初始化一个可变变量 i,初始值为 0
-
- loop {
- i += 1; // 每次循环迭代将 i 的值增加 1
-
- if i > 10 { // 检查 i 是否大于 10
- break; // 如果 i 大于 10,则退出循环
- }
- }
-
- println!("Final value of i: {}", i); // 打印 i 的最终值
- }
- fn sum(a: i32, b: i32) -> i32 {
- a + b
- }
- fn sum(a: i32, b: i32) -> i32 {
- a + b
- }
let x = if someBool { 2 } else { 4 } (1)确保所有缓冲区中的数据都被写入到标准输出(通常是终端或控制台)中
io::stdout().flush().unwrap();
(2)read_line 方法从标准输入读取用户输入并将其存储到 guess 中。如果读取失败,程序会崩溃并显示错误信息 "读取输入失败."。
- let mut guess = String::new();
- io::stdin().read_line(&mut guess).expect("读取输入失败.");
(1)? 操作符当一个函数返回 Result 或 Option 类型时,可以使用 ? 操作符来自动处理这些结果。如果结果是 Ok,则返回其中的值;如果是 Err,则返回错误并退出当前函数。
- fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
- // 尝试打开文件
- let file = File::open(filename)?;
- // 如果成功打开文件,继续执行;如果失败,返回错误并退出函数
- }
在 Rust 中,定义结构体类型时,我们声明了结构体的字段及其类型,而不是创建具体的实例。因此,不需要使用 let 或 let mut 这样的关键字。let 和 let mut关键字用于创建变量,而不是定义类型。
在这段代码中,我们定义了一个名为 Node 的泛型结构体类型,它包含三个字段:
elem:类型为 T,表示节点存储的值。next:类型为 Link,表示下一个节点的引用。prev:类型为 Link,表示前一个节点的引用。这个定义仅仅是声明了 Node 结构体的形状,并没有创建任何实际的 Node 实例。
impl 块Rust 中的 impl 块类似于其他编程语言中的 class 定义,但有一些关键的不同之处。
方法定义:
impl 块中定义的方法类似于在类中定义的方法。封装:
impl 块可以用于封装数据和行为,类似于类。数据和行为的分离:
impl 块)是分开的。没有继承:
所有权和借用:
- struct Rectangle {
- width: u32,
- height: u32,
- }
-
- impl Rectangle {
- fn new(width: u32, height: u32) -> Rectangle {
- Rectangle { width, height }
- }
-
- fn area(&self) -> u32 {
- self.width * self.height
- }
- }
-
- fn main() {
- let rect = Rectangle::new(30, 50);
- println!("The area of the rectangle is {} square pixels.", rect.area());
- }
iter(): 返回一个迭代器,该迭代器生成对数组或切片中每个元素的引用。
- let a = [1, 2, 3];
- for elem in a.iter() {
- println!("{}", elem);
- }
iter_mut(): 返回一个可变迭代器,该迭代器生成对数组或切片中每个元素的可变引用。
- let mut a = [1, 2, 3];
- for elem in a.iter_mut() {
- *elem += 1;
- }
enumerate(): 返回一个迭代器,该迭代器生成一个元组,包含每个元素的索引和值。
- let a = [1, 2, 3];
- for (i, elem) in a.iter().enumerate() {
- println!("Index: {}, Value: {}", i, elem);
- }
map(): 返回一个迭代器,该迭代器应用一个函数于每个元素。
- let a = [1, 2, 3];
- let b: Vec<_> = a.iter().map(|x| x + 1).collect();
filter(): 返回一个迭代器,该迭代器只包含满足特定条件的元素。
- let a = [1, 2, 3];
- let b: Vec<_> = a.iter().filter(|&&x| x > 1).collect();
zip(): 返回一个迭代器,该迭代器生成两个迭代器中相应元素组成的元组。
- let a = [1, 2, 3];
- let b = [4, 5, 6];
- for (x, y) in a.iter().zip(b.iter()) {
- println!("a: {}, b: {}", x, y);
- }
for_each(): 对每个元素应用一个函数(类似于 map,但不返回新的迭代器)。
- let a = [1, 2, 3];
- a.iter().for_each(|x| println!("{}", x));
闭包是一种匿名函数,它允许捕获调用者作用域中的值,可以赋值给变量,也可以作为参数传递给其他函数。闭包在许多现代编程语言中作为核心特性被广泛使用。
示例代码:
- fn main() {
- let x = 1;
- let sum = |y| x + y;
-
- assert_eq!(3, sum(2));
- }
闭包 sum 捕获了变量 x 并对其进行了操作。
传统函数实现:
- fn muuuuu(intensity: u32) -> u32 {
- println!("muuuu.....");
- thread::sleep(Duration::from_secs(2));
- intensity
- }
-
- fn workout(intensity: u32, random_number: u32) {
- // 根据 intensity 调整健身动作
- }
函数变量实现:
- fn muuuuu(intensity: u32) -> u32 {
- println!("muuuu.....");
- thread::sleep(Duration::from_secs(2));
- intensity
- }
-
- fn workout(intensity: u32, random_number: u32) {
- let action = muuuuu;
- // 根据 intensity 调整健身动作
- }
闭包实现:
- fn workout(intensity: u32, random_number: u32) {
- let action = || {
- println!("muuuu.....");
- thread::sleep(Duration::from_secs(2));
- intensity
- };
-
- // 根据 intensity 调整健身动作
- }
通过闭包可以简化代码,并捕获外部变量,使得代码更具灵活性。
闭包的定义形式如下:
- |param1, param2| {
- 语句1;
- 语句2;
- 返回表达式
- }
类型推导示例:
- let sum = |x: i32, y: i32| -> i32 {
- x + y
- };
不标注类型的闭包声明更简洁:
let sum = |x, y| x + y;
在 Rust 中,实现一个简易缓存的设计可以通过使用结构体和闭包来实现。(闭包应该作为一个变量传递,并且使用泛型和特征约束来指定它的类型。
- struct Cacher<T>
- where
- T: Fn(u32) -> u32,
- {
- query: T,
- value: Option<u32>,
- }
闭包与特征约束:
T: Fn(u32) -> u32 表示 T 是一个实现了 Fn(u32) -> u32 特征的类型,这意味着 query 是一个闭包或函数,接受一个 u32 类型的参数并返回一个 u32 类型的值。结构体设计:
query 字段是一个闭包,用于获取值。value 字段用于存储缓存的值,初始为 None。为 Cacher 结构体实现方法:
- impl<T> Cacher<T>
- where
- T: Fn(u32) -> u32,
- {
- fn new(query: T) -> Cacher<T> {
- Cacher {
- query,
- value: None,
- }
- }
-
- fn value(&mut self, arg: u32) -> u32 {
- match self.value {
- Some(v) => v,
- None => {
- let v = (self.query)(arg);
- self.value = Some(v);
- v
- }
- }
- }
- }
创建缓存实例:
Cacher::new 创建新的缓存实例,传入一个闭包或函数作为 query。查询缓存值:
value 方法首先检查 self.value 是否已有缓存值。query 获取新值,并将其存储在 self.value 中。为了支持其他类型(如 &str),可以将 u32 替换为泛型类型 E。
- struct Cacher<T, E>
- where
- T: Fn(E) -> E,
- {
- query: T,
- value: Option<E>,
- }
-
- impl<T, E> Cacher<T, E>
- where
- T: Fn(E) -> E,
- E: Copy,
- {
- fn new(query: T) -> Cacher<T, E> {
- Cacher {
- query,
- value: None,
- }
- }
-
- fn value(&mut self, arg: E) -> E {
- match self.value {
- Some(v) => v,
- None => {
- let v = (self.query)(arg);
- self.value = Some(v);
- v
- }
- }
- }
- }
FnOnce:转移所有权move || { ... }move 关键字用于指示闭包获取其捕获变量的所有权。- fn main() {
- let x = String::from("Hello");
- let consume_x = move || {
- println!("{}", x);
- // x 被转移到闭包中,闭包执行后 x 的所有权已被消耗
- };
- consume_x(); // 第一次调用成功
- // consume_x(); // 再次调用会报错,因为 x 的所有权已被消耗
- }
FnMut:可变借用|mut param| { ... }mut 关键字在参数中表示可变借用,允许闭包内部修改捕获的变量。- fn main() {
- let mut x = 0;
- let mut add_to_x = |y| {
- x += y;
- };
- add_to_x(5); // x 现在是 5
- add_to_x(3); // x 现在是 8
- }
Fn:不可变借用|| { ... }- fn main() {
- let x = 10;
- let print_x = || {
- println!("{}", x);
- };
- print_x(); // 打印 10
- print_x(); // 可以多次调用,因为 x 只是被不可变地借用
- }
捕获变量的生命周期:
闭包的生命周期:
move 关键字可以将变量的所有权移动到闭包中,使得变量的生命周期延长到与闭包相同。生命周期标注:
以下是一个示例,展示了闭包捕获变量并在闭包生命周期内使用:
- fn main() {
- let closure = create_closure();
- println!("Closure result: {}", closure(5));
- }
-
- fn create_closure() -> impl Fn(i32) -> bool {
- let x = 10;
- move |z| z < x
- }
x 被闭包捕获并使用,通过 move 关键字将其所有权移动到闭包中。create_closure 函数结束后继续有效。(2)all 方法- fn all
(&mut self, f: F) -> bool - where
- F: FnMut(Self::Item) -> bool,
它接受一个闭包 f 作为参数,并对迭代器中的每个元素应用这个闭包。all 方法会返回一个布尔值:
f 的条件,则返回 true。f 的条件,则返回 false。let chars_left = vec![false, true, false, true];
创建迭代器:
let iter = chars_left.iter();
迭代示例:
- let first = iter.next(); // Some(&false)
- let second = iter.next(); // Some(&true)
- let third = iter.next(); // Some(&false)
- let fourth = iter.next(); // Some(&true)
- let none = iter.next(); // None
- 创建迭代器:通过调用集合的
iter、iter_mut或into_iter方法创建迭代器。- 遍历:使用
for循环或while let语句。- 常用方法:
- 转换:
map、filter、enumerate、zip- 收集:
collect、fold- 检查:
all、any- 链式调用:将多个迭代器方法链式调用以实现复杂的数据处理。
map():对每个元素应用一个函数,返回一个新的迭代器。filter():过滤符合条件的元素,返回一个新的迭代器。enumerate():为迭代器中的每个元素生成一个索引,返回 (索引, 元素) 对。zip():将两个迭代器合并为一个新的迭代器,生成 (元素1, 元素2) 对。collect():将迭代器的所有元素收集到一个集合类型中,通常是一个向量(Vec)。fold():将迭代器的所有元素通过一个累积函数聚合为一个值。all():检查是否所有元素都满足一个条件。any():检查是否有任意元素满足一个条件。将多个迭代器方法链式调用,以实现复杂的数据处理。例如:过滤、映射和收集的组合。
- fn main() {
- let vec = vec![1, 2, 3, 4, 5];
-
- // 使用迭代器遍历元素
- for val in vec.iter() {
- println!("{}", val);
- }
-
- // 使用链式调用过滤和映射元素,然后收集结果
- let processed: Vec<i32> = vec.iter()
- .filter(|&&x| x % 2 == 0) // 过滤出偶数
- .map(|&x| x * 2) // 将每个偶数乘以 2
- .collect(); // 收集结果到一个向量
-
- println!("{:?}", processed); // 输出: [4, 8]
- }
在实际项目中,通常需要处理多种类型的长连接。例如,我们有一个 WEB 服务,需要接受用户的长连接,这些连接可能是 TcpStream 或 TlsStream。为了用同一个函数处理这两种连接,我们可以使用枚举来简化代码。
假设我们有以下代码:
- fn new(stream: TcpStream) {
- let mut s = stream;
- if tls {
- s = negotiate_tls(stream);
- }
-
- // websocket 是一个 WebSocket<TcpStream> 或者 WebSocket<native_tls::TlsStream<TcpStream>> 类型
- websocket = WebSocket::from_raw_socket(s, ...);
- }
通过使用枚举类型,我们可以将 TcpStream 和 TlsStream 统一化处理:
- enum WebSocketStream {
- Tcp(TcpStream),
- Tls(native_tls::TlsStream<TcpStream>),
- }
-
- fn new(stream: WebSocketStream) {
- match stream {
- WebSocketStream::Tcp(tcp_stream) => {
- // 处理 TcpStream
- let websocket = WebSocket::from_raw_socket(tcp_stream, ...);
- }
- WebSocketStream::Tls(tls_stream) => {
- // 处理 TlsStream
- let websocket = WebSocket::from_raw_socket(tls_stream, ...);
- }
- }
- }
通过这种方式,我们可以将 TcpStream 和 TlsStream 封装在一个枚举类型 WebSocketStream 中,并在同一个函数 new 中处理它们,简化了代码逻辑。
在 Rust 中,Option 类型是一种枚举,用于表示一个值可能存在(Some)或者不存在(None)
- fn main() {
- let numbers = vec![1, 2, 3];
- let empty: Vec<i32> = Vec::new();
-
- match get_first_element(&numbers) {
- Some(value) => println!("第一个元素是: {}", value),
- None => println!("数组为空"),
- }
-
- match get_first_element(&empty) {
- Some(value) => println!("第一个元素是: {}", value),
- None => println!("数组为空"),
- }
- }
Result
在 Rust 中,Result 枚举类型需要两个类型参数:
Result:表示操作的结果。
Ok(T):表示操作成功,包含类型 T 的值。Err(E):表示操作失败,包含类型 E 的错误信息。- fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
- let file = File::open(filename)?;
- let reader = BufReader::new(file);
- let mut lines = Vec::new();
-
- for line in reader.lines() {
- let line = line?;
- lines.push(line);
- }
-
- Ok(lines)
- }
使用 Ok 包装一个值时,你实际上是在创建一个 Result 类型的实例,表示操作成功,并返回该值作为 Result 的成功变体。
(5)match 表达式match 表达式是 Rust 中用于模式匹配的强大工具。它可以根据不同的模式执行不同的代码分支。
- match value {
- pattern1 => expr1,
- pattern2 => expr2,
- _ => expr3, // 通配模式,匹配所有其他情况
- }
在 Rust 中读取文件的流程通常包括以下步骤:
std::fs::File::open 方法打开文件,并处理可能的错误。std::io::BufReader 创建一个缓冲读取器。关键点:使用 BufReader 和 lines 方法逐行读取文件
- use std::fs::File;
- use std::io::{self, BufRead, BufReader};
-
- fn read_file_lines(filename: &str) -> Result<Vec<String>, io::Error> {
- let file = File::open(filename)?; // 打开文件
- let reader = BufReader::new(file); // 创建缓冲读取器
- reader.lines().collect() // 逐行读取并收集结果
- }
-
- fn main() {
- match read_file_lines("example.txt") {
- Ok(lines) => lines.iter().for_each(|line| println!("{}", line)),
- Err(e) => eprintln!("Error reading file: {}", e),
- }
- }
关键点:使用 read_to_string 方法一次性读取整个文件内容
- use std::fs::File;
- use std::io::{self, Read};
-
- fn read_file_to_string(filename: &str) -> Result<String, io::Error> {
- let mut file = File::open(filename)?; // 打开文件
- let mut contents = String::new();
- file.read_to_string(&mut contents)?; // 读取文件内容到字符串
- Ok(contents)
- }
-
- fn main() {
- match read_file_to_string("example.txt") {
- Ok(contents) => println!("{}", contents),
- Err(e) => eprintln!("Error reading file: {}", e),
- }
- }
关键点:使用 read_to_end 方法一次性读取整个文件内容到字节数组
- use std::fs::File;
- use std::io::{self, Read};
-
- fn read_file_to_bytes(filename: &str) -> Result<Vec<u8>, io::Error> {
- let mut file = File::open(filename)?; // 打开文件
- let mut contents = Vec::new();
- file.read_to_end(&mut contents)?; // 读取文件内容到字节数组
- Ok(contents)
- }
-
- fn main() {
- match read_file_to_bytes("example.txt") {
- Ok(contents) => println!("{:?}", contents),
- Err(e) => eprintln!("Error reading file: {}", e),
- }
- }
std::fs::read_to_string 直接读取整个文件到字符串关键点:使用 fs::read_to_string 直接读取文件内容到字符串
- use std::fs;
-
- fn read_file_to_string(filename: &str) -> Result<String, std::io::Error> {
- fs::read_to_string(filename) // 直接读取文件内容到字符串
- }
-
- fn main() {
- match read_file_to_string("example.txt") {
- Ok(contents) => println!("{}", contents),
- Err(e) => eprintln!("Error reading file: {}", e),
- }
- }
std::fs::read 直接读取整个文件到字节数组关键点:使用 fs::read 直接读取文件内容到字节数组
- use std::fs;
-
- fn read_file_to_bytes(filename: &str) -> Result<Vec<u8>, std::io::Error> {
- fs::read(filename) // 直接读取文件内容到字节数组
- }
-
- fn main() {
- match read_file_to_bytes("example.txt") {
- Ok(contents) => println!("{:?}", contents),
- Err(e) => eprintln!("Error reading file: {}", e),
- }
- }
BufReader 和 lines 方法。read_to_string 方法。read_to_end 方法。fs::read_to_string 方法。fs::read 方法。RefCell 概括RefCell 是 Rust 提供的一种类型,用于在不可变的上下文中实现内部可变性。它允许你在运行时执行借用检查,以确保安全地修改数据。这在某些数据结构(如链表)和特定场景(如闭包或异步编程)中非常有用。
内部可变性:
borrow() 获取不可变引用。borrow_mut() 获取可变引用。运行时借用检查:
典型用法:
- use std::cell::RefCell;
-
- let x = RefCell::new(5);
- {
- let y = x.borrow();
- println!("y: {}", *y); // 输出: y: 5
- }
- {
- let mut z = x.borrow_mut();
- *z = 10;
- println!("x: {}", x.borrow()); // 输出: x: 10
- }
Rc 的核心作用概括Rc(Reference Counted)是 Rust 提供的一种智能指针,允许多个所有者共享同一个数据。
共享所有权:
自动管理内存:
单线程环境:
Arc(Atomic Reference Counted)。数据共享:
不可变数据:
Rc 默认不允许多个可变引用。RefCell 使用。- use std::rc::Rc;
-
- fn main() {
- let data = Rc::new(5); // 创建一个 Rc 指针,包含数据 5
-
- let data1 = Rc::clone(&data); // 创建 data 的克隆引用
- let data2 = Rc::clone(&data); // 创建 data 的另一个克隆引用
-
- println!("Reference count: {}", Rc::strong_count(&data)); // 输出: 3
- println!("data: {}", data);
- println!("data1: {}", data1);
- println!("data2: {}", data2);
- }
Rc 的销毁时机对于 Rc(Reference Counted)智能指针,当一个 Rc 实例超出其作用域时,引用计数会自动减少。如果引用计数减少到零,Rc 管理的数据将被释放。
- use std::cell::RefCell;
- use std::rc::Rc;
-
- fn main() {
- {
- let data = Rc::new(RefCell::new(5)); // 创建一个包含 RefCell 的 Rc 指针
- println!("Initial reference count: {}", Rc::strong_count(&data)); // 输出: 1
-
- {
- let data1 = Rc::clone(&data); // 克隆 Rc 指针,引用计数增加到 2
- println!("Reference count after creating data1: {}", Rc::strong_count(&data)); // 输出: 2
-
- {
- let data2 = Rc::clone(&data); // 再次克隆 Rc 指针,引用计数增加到 3
- println!("Reference count after creating data2: {}", Rc::strong_count(&data)); // 输出: 3
-
- *data2.borrow_mut() = 10; // 修改数据
- println!("Modified data through data2: {}", data.borrow()); // 输出: 10
- } // data2 超出作用域,引用计数减少到 2
- println!("Reference count after data2 goes out of scope: {}", Rc::strong_count(&data)); // 输出: 2
-
- } // data1 超出作用域,引用计数减少到 1
- println!("Reference count after data1 goes out of scope: {}", Rc::strong_count(&data)); // 输出: 1
-
- } // data 超出作用域,引用计数减少到 0,数据被释放
- // 由于 data 已经被释放,不能再访问它
- }
replaceString 和 &str示例代码:
- fn main() {
- let string_replace = String::from("I like rust. Learning rust is my favorite!");
- let new_string_replace = string_replace.replace("rust", "RUST");
- dbg!(new_string_replace);
- }
运行结果:
new_string_replace = "I like RUST. Learning RUST is my favorite!"
replacenString 和 &strreplace 方法相同。示例代码:
- fn main() {
- let string_replace = "I like rust. Learning rust is my favorite!";
- let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
- dbg!(new_string_replacen);
- }
运行结果:
new_string_replacen = "I like RUST. Learning rust is my favorite!"
replace_rangeStringRange)。示例代码:
- fn main() {
- let mut string_replace_range = String::from("I like rust!");
- string_replace_range.replace_range(7..8, "R");
- dbg!(string_replace_range);
- }
运行结果:
string_replace_range = "I like Rust!"
truncate示例代码:
- fn main() {
- let mut string_truncate = String::from("测试truncate");
- string_truncate.truncate(3);
- dbg!(string_truncate);
- }
运行结果:
string_truncate = "测"
cleartruncate() 方法参数为 0。示例代码:
- fn main() {
- let mut string_clear = String::from("string clear");
- string_clear.clear();
- dbg!(string_clear);
- }
运行结果:
string_clear = ""
+ 或 += 操作符要求:
&str)。+ 操作符相当于调用了标准库中的 add 方法。特性:
mut 关键字修饰。示例代码:
- fn main() {
- let string_append = String::from("hello ");
- let string_rust = String::from("rust");
- let result = string_append + &string_rust; // string_append 的所有权被转移
- let mut result = result + "!"; // `result + "!"` 中的 `result` 是不可变的
- result += "!!!";
-
- println!("连接字符串 + -> {}", result);
- }
运行结果:
连接字符串 + -> hello rust!!!!
所有权转移示例:
- fn main() {
- let s1 = String::from("hello,");
- let s2 = String::from("world!");
- let s3 = s1 + &s2; // s1 的所有权被转移
- assert_eq!(s3, "hello,world!");
- // println!("{}", s1); // 这行代码会报错,因为 s1 的所有权已被转移
- }
连续连接示例:
- let s1 = String::from("tic");
- let s2 = String::from("tac");
- let s3 = String::from("toe");
-
- // String = String + &str + &str + &str + &str
- let s = s1 + "-" + &s2 + "-" + &s3;
s1 这个变量通过调用 add() 方法后,所有权被转移到 add() 方法里面, add() 方法调用后就被释放了,同时 s1 也被释放了。再使用 s1 就会发生错误。
format! 宏String 和 &strprint! 的用法,生成一个新的字符串。示例代码:
- fn main() {
- let s1 = "hello";
- let s2 = String::from("rust");
- let s = format!("{} {}!", s1, s2);
- println!("{}", s);
- }
运行结果:
hello rust!
所有权机制是Rust用来管理内存的一种系统,它确保了内存安全性并防止了许多常见的编程错误。以下是所有权机制的核心概念和规则:
每个值在Rust中都有一个所有者:
值在任一时刻只能有一个所有者:
当所有者离开作用域时,该值将被丢弃:
drop函数来释放内存。Copy trait 的类型赋值- let s = String::from("hello");
- let s1 = s;
s的栈上的数据所有权被转移给 s1(堆上数据仍然不变,移动语义),因此在之后使用s1会导致编译错误。方法或函数以 self 作为参数时,会获取调用者的所有权,调用后原变量失效。
类似地,函数以非引用类型参数接收变量时,也会获取其所有权。
- let s = String::from("hello");
- let s1 = s.clone();
在Rust中,使用clone方法可以进行深拷贝。深拷贝会复制堆上的数据,并在栈上创建一个新的所有权指向这块堆内存。结果是栈上和堆上都有独立的拷贝,因此两个变量互不影响。
Copy trait 的类型i32, u32, 等)f32, f64)bool)char)Copy trait)非Copy类型:
String不实现Copy特性,因为它们涉及更复杂的内存管理。Copy的类型进行直接赋值拷贝。解决方案:
Copy的类型,可以通过引用来解决所有权冲突。- fn main() {
- let x = (1, 2, (), "hello".to_string());
- let y = (&x.0, &x.1, &x.2, &x.3);
- println!("{:?}, {:?}", x, y);
- }
引用的可变性决定了你是否可以通过引用来修改所引用的值。
- let s = String::from("hello");
- let r1 = &s; // 不可变引用 r1
- let r2 = &s; // 不可变引用 r2
-
- println!("r1: {}, r2: {}", r1, r2); // 可以同时使用多个不可变引用
- let mut s = String::from("hello");
- let r1 = &mut s; // 可变引用 r1
-
- r1.push_str(", world");
-
- println!("{}", r1); // r1 修改了 s 的内容
&mut)。&)。反之,在存在不可变引用时,不允许存在可变引用。这些规则确保了在访问和修改数据时不会出现竞争条件。
- fn main() {
- let mut s = String::from("hello"); // 可变变量 `s` 被创建
- let ref1 = &s; // 创建对 `s` 的不可变引用 `ref1`
- s = String::from("goodbye"); // 尝试修改 `s` 的值
- println!("{}", ref3.to_uppercase()); // 使用 `ref3` 打印 `s` 的值
- }
一种修复方法是将 println! 语句移动到修改 s 之前,确保在修改 s 之前,所有的不可变引用都已经被使用完毕。例如:
- fn main() {
- let mut s = String::from("hello");
- let ref1 = &s;
- let ref2 = &ref1;
- let ref3 = &ref2;
- println!("{}", ref3.to_uppercase()); // 在修改 `s` 之前使用 `ref3`
- s = String::from("goodbye"); // 现在可以安全地修改 `s`
- }
返回的是一个局部变量的引用,函数作用域结束后,变量销毁
- fn drip_drop() -> &String {
- let s = String::from("hello world!");
- return &s;
- }
修改方法:直接返回所有权
- fn drip_drop() -> String {
- let s = String::from("hello world!");
- return s;
- }
v[0]返回一个引用,试图将向量中元素的引用赋值给一个所有权变量
- fn main() {
- let s1 = String::from("hello");
- let mut v = Vec::new();
- v.push(s1);
- let s2: String = v[0]; // 试图移动元素的所有权
- println!("{}", s2);
- }
解决方法:(仅读取)
- fn main() {
- let s1 = String::from("hello");
- let mut v = Vec::new();
- v.push(s1);
- let s2: &String = &v[0];
- println!("{}", s2);
- }
切片(slice)是Rust中对数组、字符串等集合部分数据的引用。它具有以下核心特性:
&[T])和可变切片(&mut [T])。- fn main() {
- let arr = [1, 2, 3, 4, 5];
- let slice = &arr[1..4]; // 引用数组的部分数据
- println!("{:?}", slice); // 输出 [2, 3, 4]
- }
- fn main() {
- let mut arr = [1, 2, 3, 4, 5];
- let slice = &mut arr[1..3]; // 可变切片,引用数组的部分数据
- slice[0] = 10;
- println!("{:?}", arr); // 输出 [1, 10, 3, 4, 5]
- }
- fn main() {
- let s = String::from("hello, world");
- let hello = &s[0..5]; // 引用字符串的部分数据
- println!("{}", hello); // 输出 "hello"
- }
- fn main() {
- let arr = [1, 2, 3, 4, 5];
- let slice = &arr[1..4];
- println!("Length: {}", slice.len()); // 输出 "Length: 3"
- println!("First element: {:?}", slice.first()); // 输出 "First element: Some(2)"
- }
创建引用:
&。- let x = 5;
- let y = &x; // 创建对 x 的不可变引用
- let z = &mut x; // 创建对 x 的可变引用(需要 x 是可变的)
函数参数传递引用:
&。- fn print_value(value: &i32) {
- println!("{}", value);
- }
-
- let x = 10;
- print_value(&x); // 传递 x 的引用
解引用:
*。- let x = 5;
- let y = &x;
- println!("{}", *y); // 解引用 y 获取 x 的值
let表达式可以用于变量解构,从复杂变量中匹配出部分内容。wrapping_*、checked_*、overflowing_*和saturating_*。wrapping_* 方法:
wrapping_add、wrapping_sub、wrapping_mul等。- let x: u8 = 255;
- let y = x.wrapping_add(1); // y == 0
checked_* 方法:
None,否则返回Some(结果)。适合需要检测并处理溢出的情况。checked_add、checked_sub、checked_mul等。- let x: u8 = 255;
- if let Some(y) = x.checked_add(1) {
- // 不会执行
- } else {
- println!("溢出检测到");
- }
Option类型来检测和处理溢出。overflowing_* 方法:
overflowing_add、overflowing_sub、overflowing_mul等。- let x: u8 = 255;
- let (y, overflowed) = x.overflowing_add(1); // y == 0, overflowed == true
saturating_* 方法:
saturating_add、saturating_sub、saturating_mul等。- let x: u8 = 255;
- let y = x.saturating_add(1); // y == 255
在Rust中,数字字面量中的下划线(_)可以用于增加可读性,它们不会影响数值的实际值。
示例:1_000.000_1 表示 1000.0001。
Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。Unicode 值的范围从 U+0000 ~ U+D7FF 和 U+E000 ~ U+10FFFF。
由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节:
在Rust中,单元类型 () 表示空值或空元组,通常用于函数不返回任何值的情况。尽管逻辑上是空的,但它在内存中占用的大小为0字节。使用 std::mem::size_of_val 可以确认这一点,例如 assert!(size_of_val(&unit) == 0);,这保证了 unit 的内存占用为0,体现了Rust中零大小类型(ZST)的概念和用途。
!,表示函数永远不会正常返回控制权。loop {}创建一个永不退出的循环。panic!:触发一个恐慌,使程序中止。std::process::exit:立即终止程序并返回指定的状态码。- // 方法一:使用无限循环
- fn never_return_fn() -> ! {
- loop {
- // 无限循环,永远不会返回
- }
- }
-
- // 方法二:调用panic!
- fn never_return_fn() -> ! {
- panic!("This function never returns!");
- }
-
- // 方法三:使用std::process::exit
- use std::process;
-
- fn never_return_fn() -> ! {
- process::exit(1); // 退出程序并返回状态码1
- }
- let x = 5; // 不可变变量
- let mut y = x; // 所有权转移,y 变为可变
- y += 1; // 修改 y 的值
部分移动:
原变量状态:
-
- fn main() {
- let t = (String::from("hello"), String::from("world"));
-
- let _s = t.0;
-
- // 仅修改下面这行代码,且不要使用 `_s`
- println!("{:?}", t.1);
- }
使用场景
& 是直接引用,用于创建一个指向某个值的引用,适用于任何需要引用的地方。ref 主要在模式匹配中使用,用于方便地在模式匹配过程中获取某个值的引用。代码简洁性和可读性
& 创建引用时,代码逻辑清晰,直接指向某个值,易于理解。ref 在模式匹配中创建引用,可以使模式匹配的代码更加简洁和直观,避免了在模式匹配外部手动创建引用的繁琐。当你在模式匹配中需要创建多个嵌套值的引用时,ref 可以大大简化代码的编写和阅读。例如:
- struct Point {
- x: i32,
- y: i32,
- }
-
- let p = Point { x: 10, y: 20 };
-
- match p {
- Point { ref x, ref y } => {
- // 在这里 x 和 y 都是引用
- println!("x: {}, y: {}", x, y);
- }
- }
在这个例子中,使用 ref 可以直接在模式匹配中创建 x 和 y 的引用。如果不使用 ref,你需要手动创建引用,这样会使代码变得更复杂:
- struct Point {
- x: i32,
- y: i32,
- }
-
- let p = Point { x: 10, y: 20 };
-
- let Point { x, y } = p;
- let x_ref = &x;
- let y_ref = &y;
-
- println!("x: {}, y: {}", x_ref, y_ref);
s.clear() 时存在对 s 的不可变引用 ch,违反借用规则。确保在修改原数据前处理完所有对数据的引用:
- fn main() {
- let mut s = String::from("hello world");
-
- // 获取第一个字符的不可变引用
- let ch = first_character(&s);
- println!("the first character is: {}", ch);
-
- // 清空字符串,在使用完不可变引用之后
- s.clear();
- }
-
- fn first_character(s: &str) -> &str {
- &s[..1]
- }
UTF-8 编码的字符可能占用多个字节,切片操作必须在字符边界上进行,否则程序会崩溃。
核心要点:
.char_indices() 方法获取字符边界,确保切片安全。示例:
- let s = "中国人";
- let a = &s[0..3]; // 正确的切片,取第一个汉字 "中"
- println!("{}", a); // 输出 "中"
从 &str 到 String:
String::from("hello, world")"hello, world".to_string()从 String 到 &str:
&s&s[..]s.as_str()这背后的原理是 Rust 的 Deref 隐式强制转换。
[u8]。chars 和 bytes 方法遍历字符串,确保正确处理字符和字节。Rust 提供了多种方法来处理字符串中的转义字符和原样字符串。以下是详细说明及核心概括。
\x 后跟两个十六进制数来表示 ASCII 字符。\u 后跟花括号中的 Unicode 码点来表示 Unicode 字符。示例代码:
- fn main() {
- // 通过 \ + 字符的十六进制表示,转义输出一个字符
- let byte_escape = "I'm writing \x52\x75\x73\x74!";
- println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
-
- // \u 可以输出一个 unicode 字符
- let unicode_codepoint = "\u{211D}";
- let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
-
- println!(
- "Unicode character {} (U+211D) is called {}",
- unicode_codepoint, character_name
- );
-
- // 换行了也会保持之前的字符串格式
- // 使用\忽略换行符
- let long_string = "String literals
- can span multiple lines.
- The linebreak and indentation here ->\
- <- can be escaped too!";
- println!("{}", long_string);
- }
运行结果:
- What are you doing? (\x3F means ?) I'm writing Rust!
- Unicode character ℝ (U+211D) is called "DOUBLE-STRUCK CAPITAL R"
- String literals
- can span multiple lines.
- The linebreak and indentation here -><- can be escaped too!
r# 和 # 包围字符串,忽略转义字符。# 包围字符串,以处理双引号和其他复杂情况。示例代码:
- fn main() {
- // 保持字符串的原样输出
- println!("{}", "hello \\x52\\x75\\x73\\x74");
-
- // 原样字符串
- let raw_str = r"Escapes don't work here: \x3F \u{211D}";
- println!("{}", raw_str);
-
- // 包含双引号的原样字符串
- let quotes = r#"And then I said: "There is no escape!""#;
- println!("{}", quotes);
-
- // 使用多个 # 处理复杂情况
- let longer_delimiter = r###"A string with "# in it. And even "##!"###;
- println!("{}", longer_delimiter);
- }
运行结果:
- hello \x52\x75\x73\x74
- Escapes don't work here: \x3F \u{211D}
- And then I said: "There is no escape!"
- A string with "# in it. And even "##!
如果你想以 Unicode 字符的方式遍历字符串,可以使用 chars 方法:
示例代码:
- fn main() {
- for c in "中国人".chars() {
- println!("{}", c);
- }
- }
输出:
- 中
- 国
- 人
如果你想查看字符串的底层字节数组,可以使用 bytes 方法:
示例代码:
- fn main() {
- for b in "中国人".bytes() {
- println!("{}", b);
- }
- }
输出:
- 228
- 184
- 173
- 229
- 155
- 189
- 228
- 186
- 186
由于 UTF-8 字符串的变长特性,从中获取子串相对复杂。Rust 标准库无法直接支持按字符边界提取子串。需要使用第三方库(如 utf8_slice)来实现。
示例代码:
- fn main() {
- let s = "holla中国人नमस्ते";
- // 使用第三方库 utf8_slice 提取子串
- let sub = utf8_slice::slice(s, 5, 8);
- println!("{}", sub); // 输出: 中国
- }
字符串字面量类型:
&str。str 类型的使用:
str 类型表示字符串的不可变视图,通常无法直接使用。&str 来处理字符串的引用。Box 和 &str 的转换:
Box 将 str 类型存储在堆上,通过引用 & 将 Box 转换为 &str。问题:Rust 默认只支持最多12个元素的元组,超过会导致编译错误。
解决方案:
基础类型与复杂类型区别:
i32)支持 Copy 特性,可以通过 [value; n] 语法初始化。String)不支持 Copy 特性,需要逐个创建元素。解决方法:使用 std::array::from_fn 函数,通过闭包生成每个元素,避免所有权问题。
原代码错误是将切片大小误认为 8 字节。应将 assert! 中的值修改为 16。
- fn main() {
- let arr: [char; 3] = ['中', '国', '人'];
- let slice = &arr[..2];
-
- // 修改数字 `8` 让代码工作
- assert!(std::mem::size_of_val(&slice) == 16);
- }
Copy 特征的字段会被拷贝,不涉及 Copy 特征的字段会发生所有权转移。传统方式:
- let user2 = User {
- active: user1.active,
- username: user1.username,
- email: String::from("another@example.com"),
- sign_in_count: user1.sign_in_count,
- };
结构体更新语法:
- let user2 = User {
- email: String::from("another@example.com"),
- ..user1
- };
Copy 特征Copy 特征:bool 和 u64 等实现了 Copy 特征的类型在赋值时会被拷贝,不会转移所有权。Copy 类型:如 String 等在赋值时会转移所有权,导致原结构体实例中的对应字段不能再被使用。- 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,
- };
- let user2 = User {
- email: String::from("another@example.com"),
- ..user1
- };
-
- // 使用了 Copy 特征的字段可以继续使用
- println!("{}", user1.active); // 依然有效
-
- // 使用了所有权转移的字段将导致错误
- // println!("{:?}", user1); // 错误:user1 的 username 字段所有权已被转移
- }
- struct File {
- name: String,
- data: Vec<u8>,
- }

从图中可以清晰地看出 File 结构体两个字段 name 和 data 分别拥有底层两个 [u8] 数组的所有权(String 类型的底层也是 [u8] 数组),通过 ptr 指针指向底层数组的内存地址,这里你可以把 ptr 指针理解为 Rust 中的引用类型。
该图片也侧面印证了:把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段。
struct 结构体名(字段类型1, 字段类型2, ...);
示例: - struct Color(i32, i32, i32);
- struct Point(i32, i32, i32);
#[derive(Debug)] 打印结构体#[derive(Debug)] 注解: 使结构体或枚举自动实现 Debug 特征,从而能够使用调试格式进行打印。为结构体派生 Debug 特征:
- #[derive(Debug)]
- struct Rectangle {
- width: u32,
- height: u32,
- }
调试打印:
dbg! 宏打印调试信息到标准错误输出 stderr: - let rect1 = Rectangle {
- width: dbg!(30 * scale),
- height: 50,
- };
- dbg!(&rect1);
println! 宏在标准输出 stdout 中打印调试信息: println!("{:?}", rect1);
as 操作符将枚举值转换为整数类型后进行比较。-
- // 修复错误
- enum Number {
- Zero,
- One,
- Two,
- }
-
- enum Number1 {
- Zero = 0,
- One,
- Two,
- }
-
- // C语言风格的枚举定义
- enum Number2 {
- Zero = 0,
- One = 1,
- Two = 2,
- }
-
-
- fn main() {
- // 通过 `as` 可以将枚举值强转为整数类型
- assert_eq!(Number::One as i32, Number1::One as i32);
- assert_eq!(Number1::One as i32, Number2::One as i32);
- }
- enum Message {
- Quit,
- Move { x: i32, y: i32 },
- Write(String),
- ChangeColor(i32, i32, i32),
- }
-
使用字段名初始化:
let msg1 = Message::Move { x: 1, y: 2 };
确保类型正确:
let msg2 = Message::Write(String::from("hello, world!"));
Option 是 Rust 中用来表示可能为空的值的类型。unwrap_or 方法可以为 Option 提供一个默认值,从而避免类型不匹配的问题。_ 是一个通配符用来匹配任何值但不绑定该值。这意味着当我们不需要使用该值时,可以用 _ 来忽略它。
unwrap_or:
Option 中提取值,如果 Option 为 None,则返回提供的默认值。let value = some_option.unwrap_or(0); unwrap_or_else:
unwrap_or,但接受一个闭包,在 Option 为 None 时执行该闭包并返回结果。- let value = some_option.unwrap_or_else(|| {
- println!("No value found, using default");
- 0
- });
expect:
Option 中提取值,如果 Option 为 None,则程序 panic 并输出自定义错误消息。let value = some_option.expect("Expected a value but found None");
break 可以在循环中返回一个值,允许在退出循环时传递该值,从而简化特定条件下的值传递。
- fn main() {
- let mut n = 1;
- let result = loop {
- let square = n * n;
- if square > 100 {
- break square; // 返回第一个大于100的平方数
- }
- n += 1;
- };
- println!("The first square greater than 100 is {}", result);
- }
模式匹配(match):
使用 match 表达式优雅地处理 Option 的所有可能情况,避免直接使用 unwrap 可能带来的 panic 问题。
- match some_option {
- Some(value) => println!("Value is: {}", value),
- None => println!("No value found"),
- }
使用模式 &mut V 去匹配一个可变引用时,你需要格外小心,因为匹配出来的 V 是一个值,而不是可变引用
- fn main() {
- let mut v = String::from("hello,");
- let r = &mut v;
-
- match r {
- // &mut value => value.push_str(" world!")
- value => value.push_str(" world!")
- }
- }
- fn print_array
Debug, const N:usize>(arr:[T;N]) { - println!("{:?}", arr);
- }
1.&str包含指针和字符串长度,两个字段共16个字节
2.String包含指针,字符串长度,容量,三个字段共24个字节
3.Rust中char均为4个字节(UTF-8)
- #![allow(incomplete_features)]
- #![feature(generic_const_exprs)]
-
- fn check_size<T>(val: T)
- where
- Assert<{ core::mem::size_of::<T>() < 768 }>: IsTrue,
- {
- //...
- }
-
- // 修复 main 函数中的错误
- fn main() {
- check_size([0u8; 767]);
- check_size([0i32; 191]);
- check_size(["hello你好"; 47]); // size of &str ?
- check_size([(); ].map(|_| "hello你好".to_string())); // size of String?
- check_size(['中'; 191]); // size of char ?
- }
-
-
-
- pub enum Assert<const CHECK: bool> {}
-
- pub trait IsTrue {}
-
- impl IsTrue for Assert<true> {}
孤儿规则规定:如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的。这意味着:
不能做的:
- impl Display for String {
- fn fmt(&self, f: &mut Formatter) -> Result {
- write!(f, "{}", self)
- }
- }
解释:Display 特征和 String 类型都定义在标准库中,而不是在当前作用域中。因此,孤儿规则禁止我们在当前作用域中为 String 实现 Display 特征。
这种 impl Trait 形式的返回值,在返回的真实类型非常复杂,你不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型)非常有用,此时就可以用 impl Trait 的方式简单返回
但这种返回值方式只能有一个具体的类型
- fn returns_summarizable() -> impl Summary {
- Weibo {
- username: String::from("sunface"),
- content: String::from(
- "m1 max太厉害了,电脑再也不会卡",
- )
- }
- }
Output 关联类型可以在以下几种情况下特别有用:
impl Trait作为函数参数通过 impl Trait 的方式来指定实现了该特征的参数:该参数能接受的类型必须要实现指定的特征。
(1)注意传入的参数需要实现相应特征
- struct Pair<T> {
- x: T,
- y: T,
- }
-
- impl<T> Pair<T> {
- fn new(x: T, y: T) -> Self {
- Self {
- x,
- y,
- }
- }
- }
-
- impl<T: std::fmt::Debug + PartialOrd> Pair<T> {
- fn cmp_display(&self) {
- if self.x >= self.y {
- println!("The largest member is x = {:?}", self.x);
- } else {
- println!("The largest member is y = {:?}", self.y);
- }
- }
- }
-
- #[derive(Debug, PartialEq, PartialOrd)]
- struct Unit(i32);
-
- fn main() {
- let pair = Pair{
- x: Unit(1),
- y: Unit(3)
- };
-
- pair.cmp_display();
- }
不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:
Self在 Rust 中,有两个self,一个指代当前的实例对象,一个指代特征或者方法类型的别名:
- trait Draw {
- fn draw(&self) -> Self;
- }
-
- #[derive(Clone)]
- struct Button;
- impl Draw for Button {
- fn draw(&self) -> Self {
- return self.clone()
- }
- }
-
- fn main() {
- let button = Button;
- let newb = button.draw();
- }
impl Trait 多个类型通过 impl Trait 返回多个类型是不被允许的,但是我们可以返回一个 dyn 特征对象来解决问题。
-
- trait Bird {
- fn quack(&self) -> String;
- }
-
- struct Duck;
- impl Duck {
- fn swim(&self) {
- println!("Look, the duck is swimming")
- }
- }
- struct Swan;
- impl Swan {
- fn fly(&self) {
- println!("Look, the duck.. oh sorry, the swan is flying")
- }
- }
-
- impl Bird for Duck {
- fn quack(&self) -> String{
- "duck duck".to_string()
- }
- }
-
- impl Bird for Swan {
- fn quack(&self) -> String{
- "swan swan".to_string()
- }
- }
-
- fn main() {
- // 填空
- let duck = Duck{};
- duck.swim();
-
- let bird = hatch_a_bird(2);
- // 变成鸟儿后,它忘记了如何游,因此以下代码会报错
- // bird.swim();
- // 但它依然可以叫唤
- assert_eq!(bird.quack(), "duck duck");
-
- let bird = hatch_a_bird(1);
- // 这只鸟儿忘了如何飞翔,因此以下代码会报错
- // bird.fly();
- // 但它也可以叫唤
- assert_eq!(bird.quack(), "swan swan");
-
- println!("Success!")
- }
-
- fn hatch_a_bird(n: u32) -> Box<dyn Bird> {
- match n {
- 1 => Box::new(Swan),
- 2 => Box::new(Duck),
- _ => Box::new(Duck),
- }
- }
-
let birds: [Box<dyn Bird>; 9] = [Box::new(Bird); 9];
Bird 是一个特性,不能直接实例化。必须使用实现了 Bird 特性的具体结构体。[Box::new(Bird); 9] 是尝试使用特征对象 Bird 来初始化数组,这在语法和逻辑上都是错误的。let birds: [Box
Box::new(Duck) 和 Box::new(Swan) 创建特征对象的数组元素是正确的做法。- trait Bird {
- fn quack(&self);
- }
-
- struct Duck;
- impl Duck {
- fn fly(&self) {
- println!("Look, the duck is flying")
- }
- }
- struct Swan;
- impl Swan {
- fn fly(&self) {
- println!("Look, the duck.. oh sorry, the swan is flying")
- }
- }
-
- impl Bird for Duck {
- fn quack(&self) {
- println!("{}", "duck duck");
- }
- }
-
- impl Bird for Swan {
- fn quack(&self) {
- println!("{}", "swan swan");
- }
- }
-
- fn main() {
- // 填空
- // 错误 let birds: [Box
;9]= [Box::new(Bird);9]; - let birds: [Box<dyn Bird>; 2] = [Box::new(Duck), Box::new(Swan)];
-
- for bird in birds {
- bird.quack();
- // 当 duck 和 swan 变成 bird 后,它们都忘了如何翱翔于天际,只记得该怎么叫唤了。。
- // 因此,以下代码会报错
- // bird.fly();
- }
- }
&dyn Trait
Box
- fn draw1(x: Box<dyn Draw>) {
- // 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法
- x.draw();
- }
draw1(Box::new(x));
静态分发(Static Dispatch):
动态分发(Dynamic Dispatch):
dyn 关键字创建特征对象。dyn Draw)的大小不固定,因为不同的类型可以实现相同的特征。&dyn Draw 或 Box。ptr 指向实例数据。vptr 指向虚表,虚表中保存了可以调用的方法指针。
- trait Foo {
- fn method(&self) -> String;
- }
-
- impl Foo for u8 {
- fn method(&self) -> String { format!("u8: {}", *self) }
- }
-
- impl Foo for String {
- fn method(&self) -> String { format!("string: {}", *self) }
- }
-
- // 通过泛型实现以下函数
- fn static_dispatch<T: Foo>(elem : T){
- elem.method();
- }
-
- // 通过特征对象实现以下函数
- fn dynamic_dispatch(elem : Box<dyn Foo>){
- elem.method();
- }
-
- // 使用至少两种方法让代码工作
- // 不要添加/删除任何代码行
- trait MyTrait {
- fn f(&self) -> Self;
- }
-
- impl MyTrait for u32 {
- fn f(&self) -> Self { 42 }
- }
-
- impl MyTrait for String {
- fn f(&self) -> Self { self.clone() }
- }
-
- fn my_function(x: Box<dyn MyTrait>) {
- x.f()
- }
-
- fn main() {
- my_function(Box::new(13_u32));
- my_function(Box::new(String::from("abc")));
-
- println!("Success!")
- }
- trait MyTrait {
- fn f(&self) -> Box<dyn MyTrait>;
- }
-
- impl MyTrait for u32 {
- fn f(&self) -> Box<dyn MyTrait> { Box::new(42) }
- }
-
- impl MyTrait for String {
- fn f(&self) -> Box<dyn MyTrait> { Box::new(self.clone()) }
- }
-
- fn my_function(x: Box<dyn MyTrait>) {
- x.f();
- }
-
- fn main() {
- my_function(Box::new(13_u32));
- my_function(Box::new(String::from("abc")));
-
- println!("Success!")
- }
- trait MyTrait {
- fn f(&self) -> Self;
- }
-
- impl MyTrait for u32 {
- fn f(&self) -> Self { 42 }
- }
-
- impl MyTrait for String {
- fn f(&self) -> Self { self.clone() }
- }
-
- fn my_function<T: MyTrait>(x: T) {
- x.f();
- }
-
- fn main() {
- my_function(13_u32);
- my_function(String::from("abc"));
-
- println!("Success!")
- }
定义: 关联类型是在特征定义中声明的类型,用于在特征的方法签名中使用。
例子:
-
- struct Container(i32, i32);
-
- // 使用关联类型实现重新实现以下特征
- // trait Contains {
- // type A;
- // type B;
-
- trait Contains {
- type A;
- type B;
- fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
- fn first(&self) -> i32;
- fn last(&self) -> i32;
- }
-
- impl Contains for Container {
- type A = i32;
- type B = i32;
- fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
- (&self.0 == number_1) && (&self.1 == number_2)
- }
- // Grab the first number.
- fn first(&self) -> i32 { self.0 }
-
- // Grab the last number.
- fn last(&self) -> i32 { self.1 }
- }
-
- fn difference (container: &dyn Contains<A = i32, B = i32>) -> i32 {
- container.last() - container.first()
- }
-
- fn main() {
- let number_1 = 3;
- let number_2 = 10;
-
- let container = Container(number_1, number_2);
-
- println!("Does container contain {} and {}: {}",
- &number_1, &number_2,
- container.contains(&number_1, &number_2));
- println!("First number: {}", container.first());
- println!("Last number: {}", container.last());
-
- println!("The difference is: {}", difference(&container));
- }
关键点:
Self::Item 用于指代实现特征时定义的具体类型。总结: 关联类型简化了特征实现中的类型定义,提高了代码的可读性和简洁性。
定义: 为泛型类型参数指定一个默认的具体类型。
例子:
- impl<T: Sub<Output = T>> Sub
> for Point { - type Output = Self;
-
- fn sub(self, other: Self) -> Self::Output {
- Point {
- x: self.x - other.x,
- y: self.y - other.y,
- }
- }
- }
T: Sub 确保泛型类型 T 必须实现 Sub trait,并且减法操作的结果类型是 T。
关键点:
定义: 在一个类型上实现多个特征时,可能会有同名的方法。可以通过完全限定语法显式调用特定特征的方法。
例子:
- trait Pilot {
- fn fly(&self);
- }
-
- trait Wizard {
- fn fly(&self);
- }
-
- impl Pilot for Human {
- fn fly(&self) {
- println!("This is your captain speaking.");
- }
- }
-
- impl Wizard for Human {
- fn fly(&self) {
- println!("Up!");
- }
- }
-
- impl Human {
- fn fly(&self) {
- println!("*waving arms furiously*");
- }
- }
调用方法:
- let person = Human;
- Pilot::fly(&person); // 调用 Pilot 特征的方法
- Wizard::fly(&person); // 调用 Wizard 特征的方法
- person.fly(); // 调用 Human 类型的方法
完全限定语法的格式如下:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
:指定类型和特征。function:要调用的方法或函数。receiver_if_method:方法的接收器(例如 self、&self 或 &mut self)。next_arg:方法或函数的其余参数。- fn main() {
- println!("A baby dog is called a {}",
::baby_name()); - }
总结: 通过显式调用和完全限定语法,可以避免方法名称冲突,明确调用的目标方法。
定义: 特征约束是指在定义一个特征时,要求实现该特征的类型也必须实现另一个特征。
例子:
-
- trait Person {
- fn name(&self) -> String;
- }
-
- // Person 是 Student 的 supertrait .
- // 实现 Student 需要同时实现 Person.
- trait Student: Person {
- fn university(&self) -> String;
- }
-
- trait Programmer {
- fn fav_language(&self) -> String;
- }
-
- // CompSciStudent (computer science student) 是 Programmer
- // 和 Student 的 subtrait. 实现 CompSciStudent 需要先实现这两个 supertraits.
- trait CompSciStudent: Programmer + Student {
- fn git_username(&self) -> String;
- }
-
- fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
- format!(
- "My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
- student.name(),
- student.university(),
- student.fav_language(),
- student.git_username()
- )
- }
-
- struct CSStudent {
- name: String,
- university: String,
- fav_language: String,
- git_username: String
- }
-
- impl Person for CSStudent {
- fn name(&self) -> String {
- self.name.clone()
- }
- }
-
- impl Student for CSStudent {
- fn university(&self) -> String {
- self.university.clone()
- }
- }
-
- impl Programmer for CSStudent {
- fn fav_language(&self) -> String {
- self.fav_language.clone()
- }
- }
-
- impl CompSciStudent for CSStudent {
- fn git_username(&self) -> String {
- self.git_username.clone()
- }
- }
-
-
- fn main() {
- let student = CSStudent {
- name: "Sunfei".to_string(),
- university: "XXX".to_string(),
- fav_language: "Rust".to_string(),
- git_username: "sunface".to_string()
- };
-
- // 填空
- println!("{}", comp_sci_student_greeting(&student));
- }
(1)trait CompSciStudent: Programmer + Student 表示 CompSciStudent 是 Programmer 和 Student 的 subtrait。实现 CompSciStudent 的类型必须同时实现 Programmer 和 Student。
(2)CSStudent 需要逐个实现了 Person、Student、Programmer 和 CompSciStudent 这些 trait。
(3)fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String 函数使用了动态调度,通过 &dyn CompSciStudent 允许在运行时确定具体类型。
关键点:
定义: newtype 模式通过创建一个新类型(元组结构体)来绕过孤儿规则,从而实现外部类型上的外部特征。
例子:
- struct Wrapper(Vec<String>);
-
- impl fmt::Display for Wrapper {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "[{}]", self.0.join(", "))
- }
- }
-
-
- use std::fmt;
-
- // 定义一个 newtype ``
- struct Pretty (String);
-
- impl fmt::Display for Pretty {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "\"{}\"", self.0.clone() + ", world")
- }
- }
-
- fn main() {
- let w = Pretty("hello".to_string());
- println!("w = {}", w);
- }
在这个例子中,我们定义了一个新的元组结构体类型 Wrapper,其内部包含一个 Vec。然后,我们为 Wrapper 实现了 fmt::Display 特征,这样就可以打印 Wrapper 包含的字符串向量。
关键点:
Vec 表示。- let v: Vec<i32> = Vec::new();
- let mut v = Vec::new();
- v.push(1); // 自动推断类型
let mut v = Vec::with_capacity(10);
let v = vec![1, 2, 3];
push 方法。 - let mut v = Vec::new();
- v.push(1);
- {
- let v = vec![1, 2, 3];
- } // v 超出作用域被删除
- let v = vec![1, 2, 3, 4, 5];
- let third: &i32 = &v[2];
- println!("第三个元素是 {}", third);
- match v.get(2) {
- Some(third) => println!("第三个元素是 {third}"),
- None => println!("去你的第三个元素,根本没有!"),
- }
None。 - let does_not_exist = &v[100]; // 报错
- let does_not_exist = v.get(100); // 安全
- let mut v = vec![1, 2, 3, 4, 5];
- let first = &v[0];
- v.push(6);
- println!("The first element is: {first}"); // 编译器报错
- let v = vec![1, 2, 3];
- for i in &v {
- println!("{i}");
- }
- let mut v = vec![1, 2, 3];
- for i in &mut v {
- *i += 10;
- }
枚举类型:
- #[derive(Debug, PartialEq)]
- enum IpAddr {
- V4(String),
- V6(String),
- }
- fn main() {
- // 填空
- let v : Vec<IpAddr>= Vec::from([IpAddr::V4("127.0.0.1".to_string()),
- IpAddr::V6("::1".to_string())]);
-
- // 枚举的比较需要派生 PartialEq 特征
- assert_eq!(v[0], IpAddr::V4("127.0.0.1".to_string()));
- assert_eq!(v[1], IpAddr::V6("::1".to_string()));
-
- println!("Success!")
- }
特征对象:
- trait IpAddr {
- fn display(&self);
- }
- struct V4(String);
- struct V6(String);
- impl IpAddr for V4 { ... }
- impl IpAddr for V6 { ... }
- let v: Vec<Box<dyn IpAddr>> = vec![
- Box::new(V4("127.0.0.1".to_string())),
- Box::new(V6("::1".to_string())),
- ];
初始化方式:
- let v = vec![0; 3];
- let v_from = Vec::from([0, 0, 0]);
管理容量:
- let mut v = Vec::with_capacity(10);
- v.reserve(100);
- v.shrink_to_fit();
常见操作:
- let mut v = vec![1, 2];
- v.insert(2, 3);
- assert_eq!(v.remove(1), 2);
- assert_eq!(v.pop(), Some(3));
- v.clear();
整数数组排序:
- let mut vec = vec![1, 5, 10, 2, 15];
- vec.sort_unstable();
浮点数数组排序:
NaN(Not a Number),它代表一个未定义或无法表示的数值。由于 NaN 值的存在,浮点数类型没有实现完全可比较的特性 Ord,而是实现了部分可比较的特性 PartialOrd。
- let mut vec = vec![1.0, 5.6, 10.3, 2.0, 15f32];
- vec.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
结构体数组排序:
- #[derive(Debug)]
- struct Person { ... }
- let mut people = vec![
- Person::new("Zoe".to_string(), 25),
- Person::new("Al".to_string(), 60),
- ...
- ];
- people.sort_unstable_by(|a, b| b.age.cmp(&a.age));
- let v1: Vec<i32> = Vec::from(arr);
- let v2: Vec<i32> = arr.to_vec();
这里使用 Vec::from 和 to_vec 方法将数组 [1, 2, 3] 转换为 Vec。
- let s = "hello".to_string();
- let v1: Vec<u8> = s.into();
- let v2 = s.into_bytes();
to_vec 和 into_bytes 方法将字符串 "hello" 转换为 Vec。
let v3: Vec<u8> = Vec::from(s);
使用 Vec::from 将字符串切片 &str 转换为 Vec。
let v4: Vec<i32> = [0; 10].into_iter().collect();
通过迭代器和 collect 方法将数组 [0; 10] 转换为 Vec。
Vec 的区别- // 切片是只读的
- // 注意:切片和 `&Vec` 是不同的类型,后者仅仅是 `Vec` 的引用,并可以通过解引用直接获取 `Vec`
- let vec_ref: &mut Vec<i32> = &mut v;
- (*vec_ref).push(4);
- let slice3 = &vec_ref[0..4]; // 修改为正确的切片范围
-
- // 填空并修复错误
- use std::collections::HashMap;
- fn main() {
- let mut scores = HashMap::new();
- scores.insert("Sunface", 98);
- scores.insert("Daniel", 95);
- scores.insert("Ashley", 69);
- scores.insert("Katie", 58);
-
- // get 返回一个 Option<&V> 枚举值
- let score = scores.get("Sunface");
- assert_eq!(score, Some(98).as_ref());
-
- if scores.contains_key("Daniel") {
- // 索引返回一个值 V
- let score = scores["Daniel"];
- assert_eq!(score, 95);
- scores.remove("Daniel");
- }
-
- assert_eq!(scores.len(), 3);
-
- for (name, score) in scores {
- println!("The score of {} is {}", name, score)
- }
- }
new 方法创建- use std::collections::HashMap;
-
- let mut my_gems = HashMap::new();
- my_gems.insert("红宝石", 1);
-
collect 方法创建- use std::collections::HashMap;
-
- let teams_list = vec![
- ("中国队".to_string(), 100),
- ("美国队".to_string(), 10),
- ("日本队".to_string(), 50),
- ];
-
- let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
- println!("{:?}", teams_map);
Copy 特征,则会被复制进 HashMap,否则所有权将被转移。- use std::collections::HashMap;
-
- let name = String::from("Sunface");
- let age = 18;
-
- let mut handsome_boys = HashMap::new();
- handsome_boys.insert(name, age);
-
- println!("{} 已经被从帅气男孩名单中除名", name); // 此处会报错,因为 `name` 的所有权已被转移
- use std::collections::HashMap;
-
- let name = String::from("Sunface");
- let age = 18;
-
- let mut handsome_boys = HashMap::new();
- handsome_boys.insert(&name, age);
-
- std::mem::drop(name); // 此处会报错,因为 `name` 的生命周期结束
- println!("因为过于无耻,{:?} 已经被除名", handsome_boys);
get 方法- use std::collections::HashMap;
-
- let mut scores = HashMap::new();
- scores.insert(String::from("Blue"), 10);
- scores.insert(String::from("Yellow"), 50);
-
- let team_name = String::from("Blue");
- let score: Option<&i32> = scores.get(&team_name);
copied 和 unwrap_or 方法- use std::collections::HashMap;
-
- let mut scores = HashMap::new();
- scores.insert("Blue", 10);
-
- let old = scores.insert("Blue", 20);
- assert_eq!(old, Some(10));
-
- let v = scores.entry("Yellow").or_insert(5);
- assert_eq!(*v, 5);
-
- let v = scores.entry("Yellow").or_insert(50);
- assert_eq!(*v, 5);
- use std::collections::HashMap;
-
- let text = "hello world wonderful world";
- let mut map = HashMap::new();
-
- for word in text.split_whitespace() {
- let count = map.entry(word).or_insert(0);
- *count += 1;
- }
-
- println!("{:?}", map);
需要注意的是,f32 和 f64 并没有实现 Hash,原因是 浮点数精度 的问题会导致它们无法进行相等比较。
如果一个集合类型的所有字段都实现了 Eq 和 Hash,那该集合类型会自动实现 Eq 和 Hash。例如 Vect 要实现 Hash,那么首先需要 T 实现 Hash。
任何实现了 Eq 和 Hash 特征的类型都可以用于 HashMap 的 key,包括:
bool (虽然很少用到,因为它只能表达两种 key)int, uint 以及它们的变体,例如 u8、i32 等String 和 &str (提示: HashMap 的 key 是 String 类型时,你其实可以使用 &str 配合 get 方法进行查询-
- // 修复错误
- // 提示: `derive` 是实现一些常用特征的好办法
- use std::collections::HashMap;
-
- #[derive(Debug, Eq, Hash,PartialEq)]
- struct Viking {
- name: String,
- country: String,
- }
-
-
- impl Viking {
- fn new(name: &str, country: &str) -> Viking {
- Viking {
- name: name.to_string(),
- country: country.to_string(),
- }
- }
- }
-
- fn main() {
- // 使用 HashMap 来存储 viking 的生命值
- let vikings = HashMap::from([
- (Viking::new("Einar", "Norway"), 25),
- (Viking::new("Olaf", "Denmark"), 24),
- (Viking::new("Harald", "Iceland"), 12),
- ]);
-
- // 使用 derive 的方式来打印 viking 的当前状态
- for (viking, health) in &vikings {
- println!("{:?} has {} hp", viking, health);
- }
- }
HashMap::with_capacity(capacity) 来初始化指定容量的 HashMap,或者使用 HashMap::new() 进行默认初始化。- use std::collections::HashMap;
-
- let mut map = HashMap::with_capacity(10); // 初始化指定容量
- map.insert("one", 1);
- map.insert("two", 2);
twox_hash::XxHash64- use std::hash::BuildHasherDefault;
- use std::collections::HashMap;
- use twox_hash::XxHash64;
-
- let mut hash: HashMap<_, _, BuildHasherDefault<XxHash64>> = Default::default();
- hash.insert(42, "the answer");
- assert_eq!(hash.get(&42), Some(&"the answer"));
生命周期(Lifetime)是 Rust 中一个重要且复杂的概念,主要用于确保引用的有效性,避免悬垂引用(Dangling References)。它描述了引用在程序中有效的范围。
生命周期标注用 ' 开头,通常使用 'a、'b 等单个字母表示。它们并不改变引用的实际生存周期,只是告诉编译器不同引用之间的关系。
基本语法:
- &i32 // 一个引用
- &'a i32 // 具有显式生命周期的引用
- &'a mut i32 // 具有显式生命周期的可变引用
标注生命周期的函数:
- fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
- if x.len() > y.len() {
- x
- } else {
- y
- }
- }
上述函数中的生命周期 'a 表示 x、y 和返回值的生命周期至少与 'a 一样长。
Rust 的借用检查器会在编译期确保引用是有效的。例如:
- {
- let r;
- {
- let x = 5;
- r = &x;
- }
- println!("r: {}", r); // 错误:`x` 不够长
- }
编译器会报错,因为 x 在内部作用域结束时被释放,而 r 引用了一个无效的 x。
在某些情况下,编译器可以自动推导生命周期,不需要显式标注。例如:
- fn first_word(s: &str) -> &str {
- let bytes = s.as_bytes();
- for (i, &item) in bytes.iter().enumerate() {
- if item == b' ' {
- return &s[0..i];
- }
- }
- &s[..]
- }
编译器自动推导出输入参数 s 和返回值具有相同的生命周期。
&self 或 &mut self,则 self 的生命周期赋给所有输出生命周期。fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
但是此时,第二条规则却无法被使用,因为输入生命周期有两个,第三条规则也不符合,因为它是函数,不是方法,因此没有 &self 参数。在套用所有规则后,编译器依然无法为返回值标注合适的生命周期,因此,编译器就会报错,提示我们需要手动标注生命周期:
- fn longest<'a>(x: &'a str, y: &str) -> &'a str {
- x
- }
结构体中的引用字段也需要标注生命周期,以确保引用在结构体的生命周期内有效。例如:
-
- #[derive(Debug)]
- struct NoCopyType {}
-
- #[derive(Debug)]
- #[allow(dead_code)]
- struct Example<'a, 'b> {
- a: &'a u32,
- b: &'b NoCopyType
- }
-
- /* 修复函数的签名 */
- fn fix_me<'a, 'b>(foo: &'a Example<'a, 'b>) -> &'b NoCopyType {
- foo.b
- }
-
- fn main()
- {
- let no_copy = NoCopyType {};
- let example = Example { a: &1, b: &no_copy };
- fix_me(&example);
- println!("Success!")
- }
'static 生命周期是一个特殊的生命周期,表示引用在整个程序运行期间有效。它常用于字符串字面量:
let s: &'static str = "I have a static lifetime.";
- use std::fmt::Display;
-
- fn longest_with_an_announcement<'a, T>(
- x: &'a str,
- y: &'a str,
- ann: T,
- ) -> &'a str
- where
- T: Display,
- {
- println!("Announcement! {}", ann);
- if x.len() > y.len() {
- x
- } else {
- y
- }
- }
在这个例子中,函数 longest_with_an_announcement 接受两个字符串切片和一个实现了 Display 特征的参数,返回具有与输入字符串相同生命周期的较长字符串。
在处理文件读写等操作时,不仅需要处理不可恢复的错误,还需要处理可恢复的错误。Rust 提供了 Result 枚举来实现这种错误处理机制。
Result 是一个包含两种可能结果的枚举:
Ok(T) 表示操作成功,并包含成功时的返回值 T。Err(E) 表示操作失败,并包含错误信息 E。定义如下:
- enum Result
{ - Ok(T),
- Err(E),
- }
以下是打开文件的基本示例:
- use std::fs::File;
-
- fn main() {
- let f = File::open("hello.txt");
- }
可以通过以下方法获取变量或函数的返回类型:
let f: u32 = File::open("hello.txt");
编译器会提示实际返回类型为 Result。
可以使用 match 语句对 Result 进行模式匹配:
- use std::fs::File;
-
- fn main() {
- let f = File::open("hello.txt");
-
- let f = match f {
- Ok(file) => file,
- Err(error) => panic!("Problem opening the file: {:?}", error),
- };
- }
这种处理方法虽然有效,但在遇到不同类型的错误时,可能需要进行更详细的处理。
可以对不同的错误类型进行分类处理:
- use std::fs::File;
- use std::io::ErrorKind;
-
- fn main() {
- 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!("Problem creating the file: {:?}", e),
- },
- other_error => panic!("Problem opening the file: {:?}", other_error),
- },
- };
- }
unwrap 和 expect在不需要处理错误的场景下,可以使用 unwrap 或 expect 来简化代码:
- use std::fs::File;
-
- fn main() {
- let f = File::open("hello.txt").unwrap();
- }
-
- fn main() {
- let f = File::open("hello.txt").expect("Failed to open hello.txt");
- }
可以通过返回 Result 类型的函数来传播错误:
- use std::fs::File;
- use std::io::{self, Read};
-
- fn read_username_from_file() -> Result<String, io::Error> {
- let f = File::open("hello.txt");
-
- let mut f = match f {
- Ok(file) => file,
- Err(e) => return Err(e),
- };
-
- let mut s = String::new();
- match f.read_to_string(&mut s) {
- Ok(_) => Ok(s),
- Err(e) => Err(e),
- }
- }
? 简化错误传播(? 还能实现链式调用,File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用)
- use std::fs::File;
- use std::io;
- use std::io::Read;
-
- fn read_username_from_file() -> Result<String, io::Error> {
- let mut s = String::new();
-
- File::open("hello.txt")?.read_to_string(&mut s)?;
-
- Ok(s)
- }
? 也可以用于 Option 类型:
- fn first(arr: &[i32]) -> Option<&i32> {
- let v = arr.get(0)?;
- Some(v)
- }
-
- fn last_char_of_first_line(text: &str) -> Option<char> {
- text.lines().next()?.chars().last()
- }
可以使 main 函数返回 Result 类型,从而使用 ? 进行错误处理:
- use std::error::Error;
- use std::fs::File;
-
- fn main() -> Result<(), Box<dyn Error>> {
- let f = File::open("hello.txt")?;
- Ok(())
- }
在 Rust 中,map 和 and_then 是两个重要的组合器(combinator),常用于处理 Result 和 Option 类型的值。这两个方法在函数式编程风格中非常常见,可以简化代码,减少重复的错误处理逻辑。下面我们详细解释这两个方法,并总结它们的核心概念。
map 方法map 方法用于对 Result 或 Option 类型中的成功值(即 Ok 或 Some)进行转换,而不改变错误值(即 Err 或 None)。
用法:
- result.map(|value| {
- // 对成功值进行操作
- })
示例:
- let result: Result<i32, ParseIntError> = "42".parse();
- let new_result = result.map(|num| num + 2);
在上面的例子中,如果 result 是 Ok(42),那么 new_result 将是 Ok(44);如果 result 是 Err(e),那么 new_result 仍然是 Err(e)。
and_then 方法and_then 方法用于链式调用多个可能失败的操作。它接收一个返回 Result 或 Option 的闭包,并将当前成功值传递给这个闭包。
用法:
- result.and_then(|value| {
- // 返回新的 Result 或 Option
- })
示例:
- let result: Result<i32, ParseIntError> = "42".parse();
- let new_result = result.and_then(|num| {
- // 假设这个函数也返回 Result
- another_function(num)
- });
在上面的例子中,如果 result 是 Ok(42),则 num 会被传递给 another_function,并返回新的 Result;如果 result 是 Err(e),则 new_result 仍然是 Err(e)。
让我们用 map 和 and_then 重写之前的 multiply 函数,以使其更简洁。
- use std::num::ParseIntError;
-
- // 使用 Result 重写后,我们使用模式匹配的方式来处理,而无需使用 `unwrap`
- // 但是这种写法实在过于啰嗦..
- fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
- match n1_str.parse::<i32>() {
- Ok(n1) => {
- match n2_str.parse::<i32>() {
- Ok(n2) => {
- Ok(n1 * n2)
- },
- Err(e) => Err(e),
- }
- },
- Err(e) => Err(e),
- }
- }
-
- // 重写上面的 `multiply` ,让它尽量简洁
- // 提示:使用 `and_then` 和 `map`
- fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {
- n1_str.parse::<i32>().and_then(|n1| {
- n2_str.parse::<i32>().map(|n2| n1 * n2)
- })
- }
-
- fn print(result: Result<i32, ParseIntError>) {
- match result {
- Ok(n) => println!("n is {}", n),
- Err(e) => println!("Error: {}", e),
- }
- }
-
- fn main() {
- let twenty = multiply1("10", "2");
- print(twenty);
-
- // 下面的调用会提供更有帮助的错误信息
- let tt = multiply("t", "2");
- print(tt);
-
- println!("Success!")
- }
类型别名在 Rust 中是一种简化代码、提高可读性和可维护性的有效方式。它可以减少代码中的冗长和重复,使代码更加清晰。
定义类型别名
类型别名使用 type 关键字来定义。例如,如果你在代码中多次使用 Result,可以将其定义为一个类型别名:
- use std::num::ParseIntError;
-
- type Res<T> = Result<T, ParseIntError>;
使用类型别名
一旦定义了类型别名,就可以在代码中使用它来替代原来的长类型名:
- fn multiply(first_number_str: &str, second_number_str: &str) -> Res<i32> {
- first_number_str.parse::<i32>().and_then(|first_number| {
- second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
- })
- }
闭包的内存分配
函数的内存管理
move 关键字:move 关键字将变量的所有权从其原始作用域移动到闭包的作用域中。Copy 类型(如 i32),闭包捕获的是变量的一个副本,而不是引用。count 副本都会递增,而外部的 count 保持不变。- fn main() {
- let mut count = 0;
-
- // 闭包 `inc` 使用 `move` 捕获了 `count` 的所有权
- // 由于 `count` 是一个 `i32` 类型,实现了 `Copy` trait,
- // 因此捕获的是 `count` 的一个副本,但这个副本在闭包的整个生命周期内是唯一的
- let mut inc = move || {
- count += 1; // 修改的是闭包内的 `count` 副本,每次调用闭包时,这个副本都会递增
- println!("`count`: {}", count); // 打印副本的值
- };
-
- inc(); // 调用闭包,闭包内的 `count` 变为 1
-
- // 尝试不可变借用原始的 `count`
- // 因为闭包捕获的是副本,原始的 `count` 仍然存在且未被借用
- let _reborrow = &count;
-
- inc(); // 再次调用闭包,闭包内的 `count` 副本变为 2
-
- // 尝试可变借用原始的 `count`
- // 由于闭包捕获的是副本,原始的 `count` 仍然存在且未被借用
- let _count_reborrowed = &mut count;
-
- // 断言外部的 `count` 仍然为 0
- // 因为闭包内部修改的是副本,外部的 `count` 未被修改
- assert_eq!(count, 0);
- }