文章目录
- 第七章: 智能指针
- 独占所有权的`Box`
- Box 在堆上存储数据
- Deref 解引用
- 解引用指针
- 解引用Box
- Drop 清理资源
- 共享所有权的Rc
- 应对内部可变性的RefCell
- 第八章:并发编程
- 多线程并发
- 线程管理
- 创建新线程
- 线程与move包
- 线程池
- 异步并发
- async/.await语法
- async-std库
- 第九章: 错误处理
- Result
第七章: 智能指针
智能指针实际上是一个结构体,他的行为类似指针,是对指针的一种封装,可以拥有元数据,并提供了额外的功能,比如自动释放堆内存。
智能指针和引用的主要区别是: 引用是一类只借用数据的指针,而智能指针在大部分情况下拥有指向的数据的所有权。
智能指针区别于普通结构体的特性在于,他实现了Deref trait 和 Drop trait ,使自身具有类似指针的行为。
Deref trait
提供了解引用的功能,使智能指针可以当作引用处理
Drop trait
提供了自动析构的功能,当智能指针离开作用域时,drop trait 容许自定义处理逻辑。常用智能指针Box 、Rc 和 RefCell
独占所有权的Box<T>
Box<T>
是指向类型为T 的堆内存分配值的智能指针,可以通过解引用操作符来获取Box中的T。当Box 超出作用域范围时,Rust 会自动调用其析构函数,销毁内部对象,并释放堆缓存
Box 在堆上存储数据
Box是独占数据的所有权,使用box::new 函数可以在堆上存储一个值,并把指向堆上的数据的指针存放在栈上。
fn main() {
let x: Box<i32> = Box::new(5);
let y: Box<i32> = x; // 会产生所有权转移,再次使用x 会报错
println!("x:{}", x);
println!("y:{}", y);
}
// 改进后的代码
fn main() {
let x: Box<i32> = Box::new(5);
let y: i32 = *x;
println!("x:{}", x);
println!("y:{}", y)
}
Deref 解引用
上面代码示例中,已经通过*来可以把实现了Deref trait 的智能指针当做引用来对待。
解引用指针
解引用指针
fn main() {
let x: i32 = 5;
let y: &i32 = &x;
assert_eq!(5, *y); // rust 不允许 i32类型与&i32类型比较 ,这里解引用 获取值
println!("pointer: {:p}", y)
}
解引用Box
解引用Box
fn main() {
let x: i32 = 5;
let y: Box<i32> = Box::new(x); // 使用Box<T> 代替引用,解引用操作符一样能工作
assert_eq!(5, *y);
println!("pointer:{:p}", y)
}
Drop 清理资源
Drop trait 自动执行一些重要的清理工作。
所有权系统确保了引用总是有效的,也确保了drop方法只会在值不再使用时被调用一次。
通过drop trait 和所有权系统,无须担心以外清理掉仍在使用的值。
要实现drop trait 必须实现drop 方法
struct Custom {
data: String,
}
impl Drop for Custom {
fn drop(&mut self) {
println!("Dropping Custom with data:{}", self.data);
}
}
fn main() {
let str1 = Custom { data: String::from("first") };
let str2 = Custom { data: String::from("second") };
println!("custom created");
println!("str1:{}", str1.data);
println!("str2:{}", str2.data);
// drop 方法自动执行类似于栈的方式,先入栈后销毁的方式。跟大部分语言是一样的。
}
共享所有权的Rc
对于每个值有且仅有一个所有者。大部分情况下,所有权是明确的。
但是特殊的场景下: 例如 图结构下,多个边可能指向同一个节点,这个节点为所有指向它的边所拥有。
Rust 提供了Rc 智能指针来引用计数,Rc 容许一个值有多个所有者,引用计数确保了只要还存在所有者,该值就保持有效。
Rc 用于希望堆上分配的数据可以供程序的多个部分读取,并且无法在编译时确定那个部分是最后使用者的场景。
Rc 是单线程引用计数指针,不是线程安全的类型,不允许将引用计数传递或共享给别的线程。
fn main() {
let x = Rc::new(5);
println!("{:p},count afer constructing x: {} ", x, Rc::strong_count(&x));
let y = x.clone();
println!("{:p},count after constructing x:{}",y,Rc::strong_count(&x));
{
let z = Rc::clone(&x);
println!("{:p},count after constructing x:{}",z,Rc::strong_count(&x));
}
println!("count after constructing x:{}",Rc::strong_count(&x));
}
应对内部可变性的RefCell
内部可变性
是Rust 的一种设计模式,他允许在不可变引用时也能改变数据。其实就是相当于 java 对象 ,实例化的对象不可以改变,但是对象内部的属性 可以通过set ,get 重新设置和获取。
Rust 提供了RefCell 来应对内部可变性模式,即值仍然是不可变的。
外部的代码不能修改其值,但值的内部能够修改其自身。
RefCell 只适用于单线程场景。
应对内部可变性的RefCell
fn main() {
let v: RefCell<Vec<i32>> = RefCell::new(vec![1, 2, 3, 4]);
println!("{:?}", v.borrow());
v.borrow_mut().push(5);
println!("{:?}", v.borrow());
}
// 在任何时候,同一作用域只允许有多个Ref<T> 或 一个 RefMut<T> 。
RefCell通过Borrow 方法创建两个Ref
fn main() {
let v: RefCell<Vec<i32>> = RefCell::new(vec![1, 2, 3, 4]);
let v_borrow_1: Ref<Vec<i32>> = v.borrow();
println!("{:?}", v_borrow_1);
let v_borrow_2: Ref<Vec<i32>> = v.borrow();
println!("{:?}",v_borrow_2)
}
第八章:并发编程
多线程并发
线程管理
Rust 支持多线程并发编程,将所有权系统和类型系统作为解决内存安全和并发问题的强有力工具。
很多问题,会在编译的时候,就回暴露出来
创建新线程
fn main() {
// 创建第一个子线程
let thread_1 = thread::spawn(|| {
for i in 1..5 {
println!("number {}", i);
thread::sleep(Duration::from_secs(2))
}
});
// 创建第二个线程
let thread_2 = thread::spawn(|| {
for i in 1..5 {
println!("number2 {}", i);
thread::sleep(Duration::from_secs(2))
}
});
// 主线程执行的代码
for i in 1..5 {
println!("number3 {}", i);
thread::sleep(Duration::from_secs(2))
}
// 阻塞主线程直到子线程执行到结束
// 位置不同,主线程阻塞的地方就不一样
thread_1.join().unwrap();
thread_2.join().unwrap();
}
线程与move包
如果需要在子线程中使用主线程的数据,可以通过闭包来获取需要的值。
子线程使用主线程数据1
fn main() {
let v = vec![1, 2, 3, 5];
let handle = thread::spawn(|| { //子线程只是借用了v 的所有权,主线程的操作可能导致子线程panic
println!("{:?}", v)
});
handle.join().unwrap()
}
子线程使用主线程数据2
fn main() {
let v = vec![1, 2, 3, 5];
let handle = thread::spawn(move || {
println!("{:?}", v)
});
handle.join().unwrap()
}
线程池
线程池是一个包,需要自己导入
threadpool = “1.8.1”
使用threadpool 多线程并发执行任务
use threadpool::ThradPool;
fn main() {
let pool = ThreadPool::new(3);
for i in 1..5 {
pool.execute(move || {
println!("number1 {}", i);
});
}
for i in 1..5 {
pool.execute(move || {
println!("number2 {}", i);
})
}
for i in 1..5 {
pool.execute(move || {
println!("number3 {}", i);
})
}
pool.join();
}
异步并发
异步并发允许在单个线程中并发执行多个任务,相比多线程并发执行多个任务,可以减少线程切换和线程共享数据时产生的开销。
async/.await语法
Rust 通过 future并发模型和async/.await 方案来实现异步并发。
此为内置语法。
- async: 通常与fn函数定义一起使用,用于创建异步函数,返回值的类型实现了Future trait,而这个 返回值需要由执行器来运行
- .await: 不阻塞当前线程,异步等待future完成。
异步函数
async fn hello_async() {
println!("hello", )
}
use futures::executor::block_on;
fn main() {
let future = hello_async();
//阻塞当前线程
block_on(future)
}
多个异步函数
async fn learn_data_structure()-> DataStructure{}
async fn learn_algorithm(data_structure: DataStructure){}
async fn learn_rust(){}
// 创建两个独立可并发执行的异步函数
async fn learn_data_structure_and_algorithm(){
let data_structure = learn_data_structure().await; // .await 不阻塞线程,,可以在lean_data_structure 阻塞时让其他future来掌控当前线程,实现单线程并发执行多个future
learn_algorithm(data_structure).await;
}
async fn async_main(){
let future1 = learn_data_structure_and_algorithm();
let future2 = learn_rust();
// 如果1 阻塞,那么2 接管当前线程,两个循环往复交替
join!(future1,future2)
}
fn main() {
block_on(async_main())
}
join! 只能在async 函数,闭包或块内使用
async-std库
简化异步编程的第三方库
async-std = “1.6.3”
使用async-std 异步并发执行任务
use async_std::task;
use async_std::task::block_on;
fn main() {
let async_1 = task::spawn(async {
for i in 1..=5{
print_async_1(i).await
}
});
let async_2 = task::spawn(async {
for i in 1..=5{
print_async_1(i).await
}
});
for i in 1..=5{
println!("number {} from the main", i);
task::block_on(async {
task::sleep(Duration::from_secs(8)).await;
});
}
task::block_on(async_1);
task::block_on(async_2);
}
async fn print_async_1(i: i32) {
println!("number {} from async_1",i);
task::sleep(Duration::from_secs(2)).await
}
async fn print_async_2(i: i32) {
println!("number {} from async_2",i);
task::sleep(Duration::from_secs(2)).await
}
下面是几个函数的用法:
- task::spawn 函数
用于生成异步任务,
use async_std::task;
let handle = task::spawn(async {
1 + 2
});
assert_eq!(handle.await,3);
- task::block_on 函数
用于阻塞当前线程直到任务执行结束
use async_std:task;
fn main(){
task::block_on(async {
println!("hello async");
});
}
- task::sleep 函数
通过非阻塞的方式让任务等待一段时间再执行
use async_std::task;
use std::time::Duration;
task::sleep(Duration::from_secs(1)).await;
第九章: 错误处理
Rust 错误分为两个主要类别:
可恢复错误
和不可恢复错误
。可恢复错误是指可以被捕捉的且能够合理解决的问题,比如读取不存在的文件、权限被拒绝。。Rust 可以通过不断尝试之前失败的操作或者选择一个备选操作让程序继续运行。
不可恢复错误: 可导致程序崩溃的错误,可视为程序的漏洞,比如 数组越界等问题。
Rust 提供了分层式错误处理方案:
- Option : 用于处理有值和无值的情况
- Result<T,E>: 用于处理可恢复错误的情况
- Panic : 不可恢复错误的情况
- Abort : 处理会发生灾难性后果的情况
Result<T,E>
enum Result<T,E>{ OK(T), Err(E), }
当Result 的值为OK 时,泛型返回成功返回的值。
err 时,E 作为调用失败返回的错误类型。
高效处理Result<T,E>
类型的值的常规处理方式是使用match 模式来匹配。
match 模式匹配对返回值进行响应处理
fn main() {
let f = File::open("hello.txt");
let file = match f {
Ok(file) => file,
Err(error) => {
panic!("Failed to open hello.txt: {:?}", error)
}
};
}
简化方法
fn main() {
let f1 = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("failed to open file");
}
处理不同类型的错误
match模式匹配处理不同类型的错误
fn main() {
let f = File::open("hello.txt");
let file = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Failed to create file: {:?}", e),
},
other_error => panic!("failed to open hello.txt: {:?}", other_error)
}
};
}
unwrap_or_else 方法处理不同类型的错误
fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("failed to create file : {:?}", error)
})
}else {
panic!("failed to open hello.txt: {:?}", other_error)
}
});
}
传播错误
fn main() {
println!("content : {}", read_from_file().unwrap())
}
fn read_from_file() -> Result<String, io:: Error> {
let f = File::open("hello.txt");
let mut file = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match file.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
上述简化代码
fn read_from_file() -> Result<String, io:: Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s);
ok(s)
}
? 会将 收到的错误类型转换为当前函数要返回的错误类型。
更加简化的写法
fn read_from_file() -> Result<String,io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
Panic
panic!(“panic”)
追踪panic
Rust 错误也是一个Stack 的形式,但是你可以手动设置栈的深度,而不需要再追溯到源码的问题
错误会提醒设置变量
RUST_BACKTRACT=1
是执行到目前为止所有被调用函数的列表。。。
捕获panic
Rust 提供了panic::catch_unwind 函数让开发者捕获panic,以便程序可以继续执行。
注意的是: 避免滥用catch_unwind作为处理错误的惯用方法,因为可能导致内存不安全
use std::panic;
fn main(){
let v = vec![1,2,3];
println!("{}",v[0]);
let result = panic::catch_unwind(||{println!("{}",v[99])};
assert!(result.is_err());
}
第十章: 模块化编程
Rust 模块化编程有两个重要的术语: crate 和module 。
crate 可以翻译为包,
module 模块 可以将crate 的代码按功能进行分组
crate管理
crate 分为 二进制crate 和库 crate 两种类型,
两者最大的区别就是:
二进制有一个main函数作为程序入口
库crate 是一组可以在其他项目中重用的模块,没有main函数
使用cargo 创建 crate
$ cargo new foo # 创建crate 二进制
$ cargo new bar --lib # 创建库
使用第三方crate
[dependencied]
async-std = "1.5.0"
就是那个样,外网速度慢,
切换国内清华等。
打开 .cargo/config 文件
[source.crates-io]
registry= "https://github.com/rust-lang/crates.io-index"
replace-with='tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
module 系统
module 系统是一个包含函数或类型定义的命名空间,用于将函数或类型定义按功能进行分组,以提高其可读性与重用性。
每一个crate默认有一个隐式的根模块,也就是main.rs 或 lib.rs。
定义模块
使用mod 关键字后根模块名来定义模块
mod chinese {
mod greetings{}
mod farewells{}
}
mod english {
mod greetings{}
mod farewells{}
}
创建多文件模块
Rust 的模块文件系统规则如下:
- 如果foo 模块没有子模块,将foo 模块的代码放在foo.rs文件中
- 如果foo 模块有子模块,有两种方式:
- 将foo模块代码放在foo.rs文件中,并将其子模块所在文件放在foo/文件夹,推荐
- 将foo模块代码放在foo/mod.rs文件中,并将其子模块放在foo/文件夹
多文件模块的层级关系
模块的可见性
添加关键字,可以让外面的可以调用
pub mod chinese;
使用use 导入模块
正常从父模块到子模块,到自己要调用的方法
use phrases-lib::chinese::greetings as cn_greetings;
use phrases_lib::english::{greetings,farewells};
模块的路径
- crate 表示当前crate ,表示从根路径开始的绝对路径
- self关键字 表示当前模块,self.a 表示 当前模块下的子模块a
- super 关键字 代表当前模块的父模块,
使用pub use 重导出
目的是 指 将深度的模块、函数和类型定义导出到上层路径,可以让外部调用更加方便
mod greetings;
pub use self::greetings::hello;
加载外部crate
[dependencies]
phrases_lib = {path= "../phrases_lib"
第十一章: 单元测试
单元测试框架
基本的单元测试框架
#[cfg(test)] // 实现条件编辑,只有在test 启动才会编译
mod tests {
#[test] // 描述这个是一个测试函数
fn it_works(){
assert_eq!(2 +2 ,4);
}
}
测试模块一般与被测试的代码放在同一个文件中,另外,测试模块有权访问父模块的私有数据,。
编写测试
标准库提供assert! /assert_eq! / assert_ne! 来编写测试用例
assert!
用于测试布尔表达式或返回值为布尔值的函数
#[cfg[test]]
mod tests {
use super::*;
#[test]
fn is_positive_test() {
assert!(is_positive(5))
}
}
assert_eq 和 assert_ne
pub fn add(x: i32, y: i32) -> i32 {
x + y + 1
}
pub fn subtract(x: i32, y: i32) -> i32 {
x - y
}
#[cfg[test]]
mod test1s {
use super::*;
#[test]
fn add_test() {
assert_eq!(add(5, 3), 8) // 使用 ==
}
#[test]
fn subtract_test() {
assert_ne!(subtract(5, 3), 8) // 使用 !=
}
}
自定义失败信息
#[test]
fn subtract_test() {
let result = subtract(5, 3);
assert_ne!(result, 8,"{} dddd",result)
}