Rust 学习 13

archive time: 2022-06-24

今天来看看智能指针

指针就是存放地址的一个变量, 而 Rust 里最常用的指针就是 引用(ref)

智能指针

智能指针行为和指针类似, 除此之外还有一些额外元数据和功能

引用计数智能指针类型可以通过记录所有者数量, 使一份数据能够被多个所有者 同时 持有

如果没有所有者, 那么数据将会自动清零

相较于引用只是 借用(borrow) 数据, 智能指针多数都 拥有 所指向的数据

StringVec<T> 是典型的智能指针

不仅有一片内存可以让用户操作, 还拥有元数据, 如容量, 和保障, 如 String 保证是合法的 UTF-8 编码

实现智能指针

智能指针通常使用 struct 实现, 实现了 DerefDrop 这两个 trait

Deref 使得智能指针可以像引用一样使用

Drop 允许自定义智能指针离开作用域后的行为

Box<T>

在之前闭包的时候我们就见过 Box<T> 这个类型了

如果我们要返回一个闭包, 我们需要使用 Box<T> 来包裹

Box 的作用就是把内容放到 Heap 上存储, Box 指向了 Heap 上的数据

通常我们使用 Box 都是因为我们需要一个知道确切大小的内容, 但是实际大小无法确定

这时候我们就可以使用 Box 包裹, Box 的大小是确定的

或者我们要转移 所有权 同时确保操作数据时数据不被复制

此外就是在泛型, 我们只关心实现了某一特定 trait, 但是不知道具体类型, 可以使用 Box

这是使用 Box 的例子, 使用 Box::new()

fn main() {
    let b = Box::new(6);
    println!("b = {}", b);
}

我们还可以使用 Box 来实现类似 Lisp 的链表类型, 即递归类型

enum List<T> {
    Cons(T, Box<List<T>>),
    Nil,
}

Deref trait

Deref 这个 trait 是一个 操作符, 对应的是解引用操作符 *

实现这个 trait, 我们就可以像引用一样来处理我们的类型

所以, 如果需要从 Box 中取出值, 可以使用 * 来实现

fn main() {
    let b = Box::new(6);
    assert_eq!(6, *b);
}

仔细看的话, 会发现 Box 实际上就是个 tuple struct


pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);

在解引用时, 对于 *b 这个操作, 在 Box 类型上会变成 *(b.deref())

因为 deref 操作只会返回值的引用, 所以还需要一个普通的解引用才能完成解引用操作

Deref Coercion

如果 T 实现了 Deref, Deref Coercion 会把 T 的引用 转变为 T 经过 Deref 操作后生成的引用

fn main() {
    let p = Box::new(String::from("Kands"));
    hello(&p);
}

fn hello(name: &str) {
    println!("{}, hello!", name);
}

// &p : &Box<String>
//    =deref=> &String
//    =deref=> &str

这个操作是在编译时自动执行的, 运行时没有什么开销

  • T: Deref<Target=U>, 允许 &T => &U
  • T: DerefMut<Target=U>, 允许 &mut T => &mut U
  • T: Deref<Target=U>, 允许 &mut T => &U

对于可变引用, 还有 DerefMut 这样一个 trait, 是类似的

Drop trait

Drop trait 可以让我们自定义离开作用域时的动作, 如资源的释放

要实现这个 trait, 只需要实现 drop(&mut self) 方法

Drop trait 在 Prelude 里, 类似 Cpp 里的析构函数

一般, drop 方法不允许我们手动调用, 但是我们可以调用 std::mem::drop 方法来提前释放值

Rc<T>

Rc<T> 是引用计数智能指针

有时一个值会有多个所有者, 如 图里面的某个节点同时属于多个边

如果需要在 Heap 上分配数据, 并且这个数据将被程序的多个部分读取(只读), 但是不知道哪部分会先用完数据, 则可以使用 Rc<T>

但是要注意, Rc<T>仅适用于单线程的

  • Rc::clone(&a) 增加引用计数
  • Rc::strong_count(&a) 获得引用计数
    • 还有 Rc::weak_count()

使用例子:

use std::rc::Rc;

enum List<T> {
    Cons(T, Rc<List<T>>),
    Nil,
}

use List::*;

fn main() {
    // Rc(5 -> 10 -> nil)
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("{}", Rc::strong_count(&a));
    // => 1

    // 3 -> a |=> 3 -> 5 -> 10 -> nil
    let b = Cons(3, Rc::clone(&a));
    println!("{}", Rc::strong_count(&a));
    // => 2

    // 4 -> a |=> 4 -> 5 -> 10 -> nil
    let c = Cons(4, Rc::clone(&a));
    println!("{}", Rc::strong_count(&a));
    // => 3
}

Rc::clone 相较于 clone(), 不会执行数据的深度拷贝操作, 只会增加引用计数, 相对较快

Rc<T> 实际上是使用不可变引用使得数据可以共享

RefCell

Rc<T> 使用的是不可变引用, 如果我们需要对共享的数据进行修改

RefCell 代表了其持有数据的唯一所有权, 而且只能在单线程使用

Box<T>Rc<T>RefCell<T>
同一数据的所有者一个多个一个
可变性, 借用检查可变, 不可变借用 (编译时检查)不可变借用 (编译时检查)可变, 不可变借用 (运行时检查)

例子如下

pub trait Messager {
    fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: 'a + Messager> {
    messager: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messager,
{
    pub fn new(messager: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messager,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;
        if percentage_of_max >= 1.0 {
            self.messager.send("Error: You're over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messager
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messager
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessager {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessager {
        fn new() -> MockMessager {
            MockMessager {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messager for MockMessager {
        fn send(&self, msg: &str) {
            self.sent_messages.borrow_mut().push(String::from(msg));
        }
    }

    #[test]
    fn it_sends_an_over_75_persent_warning_message() {
        let mock_messager = MockMessager::new();
        let mut limit_tracker = LimitTracker::new(&mock_messager, 100);

        limit_tracker.set_value(100);
        assert_eq!(mock_messager.sent_messages.borrow().len(), 1);
    }
}

RefCell 会记录存在多少个活跃的 Ref<T>RefMut<T> 智能指针, 这样就保证了借用规则

我们还可以将 RefCell 和 Rc 结合起来使用

use std::{cell::RefCell, rc::Rc};

#[derive(Debug)]
enum List<T> {
    Cons(Rc<RefCell<T>>, Rc<List<T>>),
    Nil,
}

use List::*;

fn main() {
    // * -> 5 -> *
    let value = Rc::new(RefCell::new(5));
    // 5 -> nil
    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
    // 6 -> a |=> 6 -> 5 -> nil
    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    // 10 -> a |=> 10 -> 5 -> nil
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    println!("a = {:?}", a);
    println!("b = {:?}", b);
    println!("c = {:?}", c);
    /*
     * a = Cons(RefCell { value: 5 }, Nil)
     * b = Cons(RefCell { value: 6 }, Cons(RefCell { value: 5 }, Nil))
     * c = Cons(RefCell { value: 10 }, Cons(RefCell { value: 5 }, Nil))
     */

    *value.borrow_mut() += 10;

    println!("a = {:?}", a);
    println!("b = {:?}", b);
    println!("c = {:?}", c);
    /*
     * a = Cons(RefCell { value: 15 }, Nil)
     * b = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
     * c = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))
     */
}

除此之外, 我们还可以使用 Cell<T> 使用复制数据来访问数据, Mutex<T> 实现了跨线程下的内部可变性

循环引用和内存泄漏

Rust 也是可以发生内存泄漏的, 即使用 Rc 和 RefCell 有可能实现循环引用, 从而导致内存泄漏

use std::{cell::RefCell, rc::Rc};

#[derive(Debug)]
enum List<T> {
    Cons(T, RefCell<Rc<List<T>>>),
    Nil,
}

impl<T> List<T> {
    fn tail(&self) -> Option<&RefCell<Rc<List<T>>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

use List::*;

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
    println!("a init rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
    println!("a rc count = {}", Rc::strong_count(&a));
    println!("b init rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count = {}", Rc::strong_count(&b));
    println!("a rc count = {}", Rc::strong_count(&a));
    // a  -> 5
    // ^     |
    // |     v
    // 10 <- b

    // println!("a next item = {:?}", a.tail());
    // thread 'main' has overflowed its stack
    // error: process didn't exit successfully:
    //   `target\debug\sp.exe` (exit code: 0xc00000fd, STATUS_STACK_OVERFLOW)
}

上面的例子里, a 和 b 就形成了循环引用, 导致了内存泄漏

Weak<T>

防止循环引用可以使用 Weak<T>

Rc::clone()Rc<T> 实例的 strong_count 加一, Rc<T> 的实例只有在 strong_count = 0 的情况下被清理

Rc<T> 实例通过调用 Rc::downgrade 方法可以创建值的 Weak Reference

  • 返回 Weak<T>
  • 调用 Rc::downgrade 会为 weak_count 加一

Rc<T> 使用 weak_count 来追踪还有多少 Weak<T>

但是 weak_count 不影响 Rc<T> 是否被清理, 而且弱引用不持有所有权, 强引用计数为 0 时, 将自动断开

在使用 Weak<T> 前需要保证指向的值存在, 使用 upgrade() 方法可以返回 Option<Rc<T>>

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node<T> {
    value: T,
    parent: RefCell<Weak<Node<T>>>,
    children: RefCell<Vec<Rc<Node<T>>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf)
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch)
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf)
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf)
    );
}

/*
leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0
*/

好, 今天到此为止, 之后几天复习期末, 就摸了!