本文属于我的 Rust 学习笔记 系列。
Rust 入门学习笔记以实际例子为主,讲解部分不是从零开始的,所以不建议纯萌新观看,读者最好拥有任意一种面向对象语言的基础,然后自己多多少少看过 Rust 的基本语法,刷过一点 rustlings。
来源:原子之音。当然也包含个人的一些补充。
视频
代码
Rust 进阶学习笔记以及实战的来源则五花八门,将会标注在下一行⬇️。
本节出处:原子之音 视频 代码纯手打,不过都跑通验证过,应该没什么问题。
其实感觉进阶学习的第一篇不应该是智能指针,毕竟集合、IO 什么的都还没看。不过没办法,谁让我对这个最感兴趣呢,就先学它了。
结果看完没有特别理解,主要大部分时候根本想不起来要用智能指针。还是要找点题做做。
学完多线程后回来看,会容易理解得多。
智能指针
指针
Rust 最常见的指针是引用,裸指针被 Rust 判定为不安全,需要加上unsafe
才能使用。
智能指针的概念起源于 C++,它们提供了额外的功能和安全性保证,以帮助管理内存和数据。
在 Rust 中,智能指针是一种封装了对动态分配内存的所有权和生命周期管理的数据类型。
智能指针通常封装了一个原始指针,并提供了一些额外的功能,比如引用计数、所有权转移、生命周期管理等。
原始指针(裸指针)
由于 Rust 的所有权机制,使用原始指针是有风险的。
使用原始指针时,需要用as
来标注类型以及可变/不可变。解引用则需要unsafe
。
也不需要太过恐惧unsafe
,如果确实有需要,该用就用。后续会有一节专门讲unsafe
。
fn main() {
let x: usize = 1;
let raw1: *const usize = &x as *const usize;
let raw2: *const usize = &x;
let some_usize1 = unsafe { *raw1 };
println!("{some_usize1}");
let some_usize2 = unsafe { *raw2 };
println!("{some_usize2}");
let mut y: usize = 2;
let raw_mut1: *mut usize = &mut y as *mut usize;
let raw_mut2: *mut usize = &mut y;
let some_mut_usize1 = unsafe { *raw_mut1 };
println!("{some_mut_usize1}");
let some_mut_usize2 = unsafe { *raw_mut2 };
println!("{some_mut_usize2}");
}
智能指针
智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了Deref
和Drop
特质:
- Deref:可以让智能指针像引用那样工作,这样就可以写出同时支持智能指针和引用的代码,例如 *T
- Drop:允许指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
智能指针究竟智能在何处?答:
常见智能指针
-
Box
是最简单的智能指针,只是将数据存储在堆上。
-
Rc
指针是智能指针的核心,完成共享所有权的功能
Weak
:弱版的Rc
Arc
:多线程的Rc
Mutex
:提供可变性的Rc
-
Cell
和RefCell
用于内部可变性。
Box 指针
数据存储在堆上的情况
- 编译期数据大小无法确定
- 数据太大,copy 浪费资源
- 需要 dyn Trait Object 而非具体类型
例子
trait Animal {
fn eat(&self);
}
#[derive(Debug)]
struct Cat {
children: Option<Box<Cat>>,
}
impl Animal for Cat {
fn eat(&self) {
println!("cat is eating");
}
}
fn main() {
let cat = Box::new(Cat { children: None });
println!("{:?}", cat); let t: Box<dyn Animal>;
t = Box::new(Cat {
children: Some(cat),
});
t.eat();
}
Rc 指针
Rc 指针也叫计数指针,位于std::rc::Rc
下,是一个存储引用计数的指针。
Rc 指针主要追踪两个方向:
Rc::clone
会创建一个新的引用,让 Rc 计数加一。
use std::rc::Rc;
#[derive(Debug)]
struct Cat {}
fn main() {
let cat1 = Cat {};
let cat2 = Cat {};
let cat3 = Cat {};
let cat1 = Rc::new(Cat {});
let cat2 = Rc::new(Cat {});
let cat3 = Rc::new(Cat {});
let cat_vec1 = vec![cat1, Rc::clone(&cat2)];
let cat_vec2 = vec![cat2, cat3];
println!("{:?}", cat_vec1);
println!("{:?}", cat_vec2);
let cat1 = Rc::new(Cat {});
let cat2 = Rc::new(Cat {});
let cat3 = Rc::new(Cat {});
let cat_vec1 = vec![cat1, Rc::clone(&cat2)];
let cat_vec2 = vec![Rc::clone(&cat2), cat3];
println!("{:?}", cat_vec1);
println!("{:?}", cat_vec2);
println!("{}", Rc::strong_count(&cat2)); std::mem::drop(cat_vec2);
println!("{}", Rc::strong_count(&cat2)); }
Cell 与 RefCell
Cell 与 RefCell 用于内部可变性,说人话就是能对一个不可变的值进行可变借用。它们带来了灵活性但同时也造成了一些安全隐患。
Cell 只适用于 Copy 类型,用于提供值,而 RefCell 用于提供引用。
RefCell
RefCell 会造成 panic。
RefCell 在编译期不会报错,应慎重使用。
例子
use std::cell::{Cell, RefCell};
fn main(){
let c = Cell::new("old");
let c1: &str = c.get();
println!("{c1}"); c.set("new");
let c2: &str = c.get();
println!("{c1} {c2}"); let c = Cell::new(String::from("rust"));
let rc = RefCell::new(String::from("rust"));
let r1 = rc.borrow();
println!("{}", r1); let rc = RefCell::new(String::from("rust"));
let mut r2 = rc.borrow_mut();
r2.push_str(" new");
println!("{}", r2); }
Weak 指针
Weak 指针是指持有对分配数据的非拥有引用的指针。
与之相对,Rc 指针是拥有所有权的。
Weak 指针主要用于解决 Rc 指针的循环引用(Reference Cycle)问题。
相互转换
- Rc -> Weak:
Rc::upgrade
- Weak -> Rc:
Rc::downgrade
Weak 同样也有引用计数,可以通过weak_count()
查看。
例子
use std::{cell::RefCell, rc::{Rc, Weak}};
#[derive(Debug)]
struct People {
age: usize,
parent: RefCell<Weak<People>>,
children: RefCell<Vec<Rc<People>>>,
}
fn main(){
let son = Rc::new(People{
age: 10,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!("son parent = {:?}", son.parent.borrow()); println!("son parent = {:?}", son.parent.borrow().upgrade()); println!("{} {}", Rc::strong_count(&son), Rc::weak_count(&son));
let father = Rc::new(People{
age: 40,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&son)]),
});
println!("{} {}", Rc::strong_count(&son), Rc::weak_count(&son));
*son.parent.borrow_mut() = Rc::downgrade(&father);
println!("son parent = {:?}", son.parent.borrow().upgrade());
println!("{} {}", Rc::strong_count(&son), Rc::weak_count(&son)); println!("{} {}", Rc::strong_count(&father), Rc::weak_count(&father)); }
Arc 指针
Rc 指针不支持 Send 和 Sync 特质,在多线程环境下会产生数据竞争(Data Race)。
Arc 实现了引用计数的原子化操作,是线程安全的,但会带来较大性能损耗。
Arc 位于std::sync::Arc
下,注意这个包里同样存在std::sync::Weak
,作用和 Rc 的 Weak 完全相同。
例子
use std::{clone, sync::{Arc, Weak}, thread::{self, Thread}};
#[derive(Debug)]
struct Owner {
name: String,
}
#[derive(Debug)]
struct Dog {
owner: Arc<Owner>,
}
fn main() {
let someone = Arc::new(Owner{
name: "tom".to_string(),
});
for i in 0..10 {
let someone = Arc::clone(&someone);
let join_handle = thread::spawn(move || {
let yellow = Arc::new(Dog{
owner: Arc::clone(&someone),
});
let black = Arc::new(Dog{
owner: Arc::clone(&someone),
});
println!("yellow owner {}", yellow.owner.name);
println!("black owner {}", black.owner.name);
println!("Thread {i} end");
});
}
}
Mutex
想要加入可变的功能就需要 Mutex。
可变不可变是编译器对引用的附加限制,使用可变会增加性能损耗。
互斥锁
Mutex(是mutual exclusion 的缩写)意思是互斥锁,其主要作用是让多个线程并发的访问同一个值变成了排队访问:同一时间,只允许一个线程 A 访问该值,其它线程需要等待 A 访问完成后才能继续。
这里就是先提一下,后续多线程一节中会更详细描述 Mutex。
例子
use std::{clone, sync::{Arc, Mutex, Weak}, thread::{self, Thread}};
#[derive(Debug)]
struct Owner {
name: String,
dogs: Mutex<Vec<Weak<Dog>>>,
}
#[derive(Debug)]
struct Dog {
name: String,
owner: Arc<Owner>,
}
fn main() {
let someone = Arc::new(Owner{
name: "tom".to_string(),
dogs: Mutex::new(vec![]),
});
let yellow = Arc::new(Dog{
name: "yel".to_string(),
owner: Arc::clone(&someone),
});
let black = Arc::new(Dog{
name: "blc".to_string(),
owner: Arc::clone(&someone),
});
for i in 0..10 {
let someone = Arc::clone(&someone);
let yellow = Arc::clone(&yellow);
let black = Arc::clone(&black);
let join_handle = thread::spawn(move || {
let mut guard = someone.dogs.lock().unwrap();
guard.push(Arc::downgrade(&yellow));
guard.push(Arc::downgrade(&black));
println!("{} {}", guard[0].upgrade().unwrap().name, guard[1].upgrade().unwrap().name);
println!("Thread {i} end");
});
}
}