Bootstrap

【rCore OS 开源操作系统】Rust 智能指针

前置知识点

何为“智能”

在 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"),
        }
    }
}

;