前置知识点
何为“智能”
在 Rust 中,“智能指针”是指那些实现了特定智能行为的指针类型。这些智能行为通常包括内存管理、生命周期跟踪以及所有权转移等。
常见智能指针
Box
Box<T>
是 Rust 中最简单的智能指针类型之一,它用于堆分配的内存。Box<T>
允许你在堆上分配类型 T 的对象,并且它实现了 Deref trait
,使得你可以像操作栈上的对象一样操作堆上的对象。
说人话:就是用这个,可以把本来该放到栈上的内容,放到堆上
let boxed_num = Box::new(5); // 堆上放一个 5,然后 boxed_num 就是引用
println!("Boxed number is: {}", *boxed_num); // 所以这里要解引用
Rc 和 Arc
前者后者的区别就是,前者用于一般场景,后者是 Aotomic 所以用于多线程常见
Rc<T>
(Reference Counted)用于共享所有权的情况,即多个地方需要访问同一个数据。它通过引用计数来跟踪有多少个 Rc
指向同一个数据。
Arc<T>
(Atomic Rc
)与 Rc<T>
类似,但它是线程安全的,适用于多线程环境中共享数据。
说人话就是,一个堆上的数据,要被多处共享,但是 rust 默认情况下有个所有权机制,直接共享就有问题要报错,要用这个包一下才能共享
let rc_str = Rc::new(String::from("hello"));
let clone = Rc::clone(&rc_str);
println!("Original string: {}", rc_str);
println!("Cloned string: {}", clone);
Arc<T>
也是同理,此处不再赘述。后面在练习题中看具体使用。
RefCell
RefCell<T>
提供了对数据的可变借用,这对于在单线程中打破 Rust 的借用规则很有用。RefCell<T>
使用运行时检查来保证互斥性。
RefCell<T>
通常用于需要在运行时决定借用关系的情况,例如在递归数据结构中。
如果你是做 Java 的,一定很熟悉多线程加法!对,也就是那么个道理。
具体的代码放到题目中体现,看看 Rust 如何用RefCell<T>
实现
Cow
Cow<T>
是Clone and Write
的缩写。
作者真是个取名天才, 这谁能想得到啊…
Cow 是一个智能指针类型,它允许你在不进行克隆的情况下处理字符串或其他数据类型。具体来说,Cow 可以在以下两种情况下工作:
- 借用(Borrow):当数据不需要被修改时,Cow 只是一个借用。
- 克隆(Clone):当数据需要被修改时,Cow 会克隆数据并提供一个可变版本。
我称之为,“懒克隆”
代码看后面的题目。
let original_str = "Hello, world!";
// 借用字符串
let borrowed_str = Cow::from(original_str);
println!("Borrowed str: {:?}", borrowed_str);
// 修改字符串
let modified_str = Cow::from("Hello, Rustaceans!");
println!("Modified str: {:?}", modified_str);
// 如果需要修改,`Cow` 会克隆字符串
let mut mutable_str = Cow::from("Hello, Rust!");
mutable_str.to_mut().push_str("aceans");
println!("Mutable str after modification: {:?}", mutable_str);
Weak
Weak<T>
与 Rc<T>
或 Arc<T>
结合使用,当不再需要强引用时,可以使用 Weak<T>
来保持对对象的弱引用,这样即使有弱引用存在,对象也可以被垃圾回收。
这对于实现某些设计模式(如观察者模式)很有用。
感觉也不怎么用得到这个…学前端学了这么久唯一的感受就是在 Vue 的实现里有用过,其他还真是一概不知…
NonNull
NonNull<T>
是一个非空指针,它并不包含任何智能行为,但是它可以用来表示未经过 Rust 内存管理系统管理的裸指针。
这对于与 C/C++ 代码交互或操作底层内存时很有用。
(挠头)…感觉也没什么用
练习题
Arc
题目
来吧,实现一些多线程加法。
// arc1.rs
//
// In this exercise, we are given a Vec of u32 called "numbers" with values
// ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this
// set of numbers within 8 different threads simultaneously. Each thread is
// going to get the sum of every eighth value, with an offset.
//
// The first thread (offset 0), will sum 0, 8, 16, ...
// The second thread (offset 1), will sum 1, 9, 17, ...
// The third thread (offset 2), will sum 2, 10, 18, ...
// ...
// The eighth thread (offset 7), will sum 7, 15, 23, ...
//
// Because we are using threads, our values need to be thread-safe. Therefore,
// we are using Arc. We need to make a change in each of the two TODOs.
//
// Make this code compile by filling in a value for `shared_numbers` where the
// first TODO comment is, and create an initial binding for `child_numbers`
// where the second TODO comment is. Try not to create any copies of the
// `numbers` Vec!
//
// Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
#![forbid(unused_imports)] // Do not change this, (or the next) line.
use std::sync::Arc;
use std::thread;
fn main() {
let numbers: Vec<_> = (0..100u32).collect();
let shared_numbers = // TODO
let mut joinhandles = Vec::new();
for offset in 0..8 {
let child_numbers = // TODO
joinhandles.push(thread::spawn(move || {
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
println!("Sum of offset {} is {}", offset, sum);
}));
}
for handle in joinhandles.into_iter() {
handle.join().unwrap();
}
}
题解
#![forbid(unused_imports)] // Do not change this, (or the next) line.
use std::sync::Arc;
use std::thread;
fn main() {
// collect 会把 迭代器 转为 Vec<_> 的 vec(是的,根据类型推导
let numbers: Vec<_> = (0..100u32).collect();
// arc 和 rc 都是支持计数的,但是 arc 是原子的、线程安全的,而 rc 不是。
let shared_numbers = Arc::new(numbers);
let mut joinhandles = Vec::new();
for offset in 0..8 {
// 这里为啥不直接用 shared_numbers 呢?
// 是因为借用检查:Rust 的借用检查器(borrow checker)会阻止你直接将 shared_numbers 传入多个线程中的闭包,
// 因为这会导致多个线程同时访问同一个引用——这在 Rust 中是不允许的
let child_numbers = Arc::clone(&shared_numbers);
//在 Rust 中,闭包默认捕获外部作用域中的变量的方式有两种: 借用(默认情况,只读这些变量) 和 移动(可以改这些变量)
// Rust 不允许多个线程同时访问同一个引用
// 这里由于是使用基本数据类型——天然支持 copy trait,所以可以直接通过移动获取一份副本
joinhandles.push(thread::spawn(move || {
// iter() 返回的是 &u32,filter 一般是都是 &xxx 引用,所以二者叠加就是 &&n
let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
println!("Sum of offset {} is {}", offset, sum);
}));
}
for handle in joinhandles.into_iter() {
handle.join().unwrap();
}
}
Rc
题目
主要是引用计数的知识点。
用 RC:clone
去克隆一个引用。
场景说实话有点难,好几次没记住行星名字…反复对照了好几次…
// rc1.rs
//
// In this exercise, we want to express the concept of multiple owners via the
// Rc<T> type. This is a model of our solar system - there is a Sun type and
// multiple Planets. The Planets take ownership of the sun, indicating that they
// revolve around the sun.
//
// Make this code compile by using the proper Rc primitives to express that the
// sun has multiple owners.
//
// Execute `rustlings hint rc1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::rc::Rc;
#[derive(Debug)]
struct Sun {}
#[derive(Debug)]
enum Planet {
Mercury(Rc<Sun>),
Venus(Rc<Sun>),
Earth(Rc<Sun>),
Mars(Rc<Sun>),
Jupiter(Rc<Sun>),
Saturn(Rc<Sun>),
Uranus(Rc<Sun>),
Neptune(Rc<Sun>),
}
impl Planet {
fn details(&self) {
println!("Hi from {:?}!", self)
}
}
fn main() {
let sun = Rc::new(Sun {});
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
let mercury = Planet::Mercury(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
mercury.details();
let venus = Planet::Venus(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
venus.details();
let earth = Planet::Earth(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
earth.details();
let mars = Planet::Mars(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
mars.details();
let jupiter = Planet::Jupiter(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
jupiter.details();
// TODO
let saturn = Planet::Saturn(Rc::new(Sun {}));
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
saturn.details();
// TODO
let uranus = Planet::Uranus(Rc::new(Sun {}));
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
uranus.details();
// TODO
let neptune = Planet::Neptune(Rc::new(Sun {}));
println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
neptune.details();
assert_eq!(Rc::strong_count(&sun), 9);
drop(neptune);
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
drop(uranus);
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
drop(saturn);
println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
drop(jupiter);
println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
drop(mars);
println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
// TODO
println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
// TODO
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
// TODO
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
assert_eq!(Rc::strong_count(&sun), 1);
}
题解
修改也就是复用Sun引用就好,而不是新建一个引用。
这里还涉及到一个全局函数 drop
, 用来手动析构,同时可以让Rc
的引用减一。
fn main() {
let sun = Rc::new(Sun {});
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
// 省略 部分 代码!!!!
// 省略 部分 代码!!!!
// 省略 部分 代码!!!!
// TODO
let saturn = Planet::Saturn(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
saturn.details();
// TODO
let uranus = Planet::Uranus(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
uranus.details();
// TODO
let neptune = Planet::Neptune(Rc::clone(&sun));
println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
neptune.details();
assert_eq!(Rc::strong_count(&sun), 9);
drop(neptune);
println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
// 省略 部分 代码!!!!
// 省略 部分 代码!!!!
// 省略 部分 代码!!!!
drop(mars);
println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
// TODO
drop(mercury);
println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
// TODO
drop(venus);
println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
// TODO
drop(earth);
println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference
assert_eq!(Rc::strong_count(&sun), 1);
}
Box
题目
这个题目好就好在,让我知道了什么场景下会用到 Box<T>
。
是啊,什么场景下不能放栈上而要放到堆上?
什么,你说数据结构超级大的时候?
那这会不会是设计不合理呢,什么栈上数据结构能 辣么大?
// box1.rs
//
// At compile time, Rust needs to know how much space a type takes up. This
// becomes problematic for recursive types, where a value can have as part of
// itself another value of the same type. To get around the issue, we can use a
// `Box` - a smart pointer used to store data on the heap, which also allows us
// to wrap a recursive type.
//
// The recursive type we're implementing in this exercise is the `cons list` - a
// data structure frequently found in functional programming languages. Each
// item in a cons list contains two elements: the value of the current item and
// the next item. The last item is a value called `Nil`.
//
// Step 1: use a `Box` in the enum definition to make the code compile
// Step 2: create both empty and non-empty cons lists by replacing `todo!()`
//
// Note: the tests should not be changed
//
// Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
#[derive(PartialEq, Debug)]
pub enum List {
Cons(i32, List),
Nil,
}
fn main() {
println!("This is an empty cons list: {:?}", create_empty_list());
println!(
"This is a non-empty cons list: {:?}",
create_non_empty_list()
);
}
pub fn create_empty_list() -> List {
todo!()
}
pub fn create_non_empty_list() -> List {
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_empty_list() {
assert_eq!(List::Nil, create_empty_list())
}
#[test]
fn test_create_non_empty_list() {
assert_ne!(create_empty_list(), create_non_empty_list())
}
}
题解
我的了个 Holy Gosh 啊…竟然是递归数据结构…确实没想到…
不过这样说来,rust 竟然是把 enum
这类东西放栈上,有点离谱…
// Box 是一种智能指针,让我们直接在堆上分配内存,绕过栈内存的大小限制(放到堆上
// 比如下面这个 List,可以看到它的 Cons 成员中还有 List,这就产生了递归,Rust 会认为在栈上无法存下,所以需要使用 Box 改到堆上
#[derive(PartialEq, Debug)]
pub enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
println!("This is an empty cons list: {:?}", create_empty_list());
println!(
"This is a non-empty cons list: {:?}",
create_non_empty_list()
);
}
pub fn create_empty_list() -> List {
List::Nil
}
pub fn create_non_empty_list() -> List {
List::Cons(123123, Box::new(List::Nil))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_empty_list() {
assert_eq!(List::Nil, create_empty_list())
}
#[test]
fn test_create_non_empty_list() {
assert_ne!(create_empty_list(), create_non_empty_list())
}
}
Cow
题目
// cow1.rs
//
// This exercise explores the Cow, or Clone-On-Write type. Cow is a
// clone-on-write smart pointer. It can enclose and provide immutable access to
// borrowed data, and clone the data lazily when mutation or ownership is
// required. The type is designed to work with general borrowed data via the
// Borrow trait.
//
// This exercise is meant to show you what to expect when passing data to Cow.
// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the
// TODO markers.
//
// Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::borrow::Cow;
fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// Clones into a vector if not already owned.
input.to_mut()[i] = -v;
}
}
input
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reference_mutation() -> Result<(), &'static str> {
// Clone occurs because `input` needs to be mutated.
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn reference_no_mutation() -> Result<(), &'static str> {
// No clone occurs because `input` doesn't need to be mutated.
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]);
match abs_all(&mut input) {
// TODO
}
}
#[test]
fn owned_no_mutation() -> Result<(), &'static str> {
// We can also pass `slice` without `&` so Cow owns it directly. In this
// case no mutation occurs and thus also no clone, but the result is
// still owned because it was never borrowed or mutated.
let slice = vec![0, 1, 2];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
// TODO
}
}
#[test]
fn owned_mutation() -> Result<(), &'static str> {
// Of course this is also the case if a mutation does occur. In this
// case the call to `to_mut()` returns a reference to the same data as
// before.
let slice = vec![-1, 0, 1];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
// TODO
}
}
}
题解
// cow1.rs
//
// This exercise explores the Cow, or Clone-On-Write type. Cow is a
// clone-on-write smart pointer. It can enclose and provide immutable access to
// borrowed data, and clone the data lazily when mutation or ownership is
// required. The type is designed to work with general borrowed data via the
// Borrow trait.
//
// This exercise is meant to show you what to expect when passing data to Cow.
// Fix the unit tests by checking for Cow::Owned(_) and Cow::Borrowed(_) at the
// TODO markers.
//
// Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint.
use std::borrow::Cow;
fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// Clones into a vector if not already owned.
input.to_mut()[i] = -v;
}
}
input
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reference_mutation() -> Result<(), &'static str> {
// Clone occurs because `input` needs to be mutated.
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
// 这里用了 Cow::Owned 相当于会拷贝一份
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn reference_no_mutation() -> Result<(), &'static str> {
// No clone occurs because `input` doesn't need to be mutated.
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]);
match abs_all(&mut input) {
Cow::Borrowed(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn owned_no_mutation() -> Result<(), &'static str> {
// We can also pass `slice` without `&` so Cow owns it directly. In this
// case no mutation occurs and thus also no clone, but the result is
// still owned because it was never borrowed or mutated.
let slice = vec![0, 1, 2];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
#[test]
fn owned_mutation() -> Result<(), &'static str> {
// Of course this is also the case if a mutation does occur. In this
// case the call to `to_mut()` returns a reference to the same data as
// before.
let slice = vec![-1, 0, 1];
let mut input = Cow::from(slice);
match abs_all(&mut input) {
Cow::Owned(_) => Ok(()),
_ => Err("Expected owned value"),
}
}
}