CRust学习笔记:智能指针和内部可变性

电子说

1.2w人已加入

描述

本系列文章是Jon Gjengset发布的CRust of Rust系列视频的学习笔记,CRust of Rust是一系列持续更新的Rust中级教程。

我们将通过手动实现Cell,RefCell来学习智能指针及内部可变性。

新建一个项目:

cargo new --lib pointers
  手动实现Cell 在src目录下新建一个cell.rs文件,然后在lib.rs中加入:
1mod cell;
在cell.rs中写入如下代码:
 1use std::UnsafeCell;
 2
 3pub struct Cell {
 4    value: UnsafeCell,
 5}
 6
 7impl Cell {
 8    pub fn new(value: T) -> Self {
 9        Cell { 
10            value: UnsafeCell::new(value),
11        }
12    }
13
14    pub fn set(&self, value: T) {
15        unsafe {
16            *self.value.get() = value
17        };
18    }
19
20    pub fn get(&self) -> T where T: Copy{
21        unsafe {
22            *self.value.get()
23        }
24    }
25}

 

实现内部可变性需要一个特殊的cell类型,叫作UnsafeCell,它是Rust的核心原语。顾名思义它是不安全的,它持有类型,通过它可以获得类型的原生独占指针。我们使用UnsafeCell类型可以通过共享引用转变成独占引用来改变数据。

在set和get方法中需要获取类型的原生的指针,因此需要使用unsafe块。由于UnsafeCell实现了!Sync trait,表示不能安全的跨线程共享引用。

测试代码如下:

 1#[cfg(test)]
 2mod test {
 3    use super::Cell;
 4
 5    #[test]
 6    fn cell_test() {
 7        let mut x = Cell::new(42);
 8        let i = x.get();
 9        x.set(43);
10
11        assert_eq!(i, 42);
12    }
13}
执行cargo test,测试通过。     手动实现RefCell

 

RefCell的文档描述是:可以改变内存位置,可以动态检查借用规则。也就是说可以在运行期检查借用规则。

在src目录下新建一个refcell.rs文件,然后在lib.rs中加入:

1mod refcell;
在refcell.rs中写入如下代码:
 1use std::UnsafeCell;
 2use crate::Cell;
 3
 4#[derive(Clone, Copy)]
 5enum RefState {
 6    Unshared,
 7    Shared(usize),
 8    Exclusive,
 9}
10
11pub struct RefCell {
12    value: UnsafeCell,
13    state: Cell,
14}
15
16impl RefCell {
17    pub fn new(value: T) -> Self {
18        Self { 
19            value: UnsafeCell::new(value),
20            state: Cell::new(RefState::Unshared),
21        }
22    }
23
24    pub fn borrow(&self) -> Option<&T> {
25        None
26    }
27
28    pub fn borrow_mut(&self) -> Option<&mut T> {
29        None
30    }
31}

 

这是RefCell 最基本的API。borrow和borrow_mut方法的返回值使用Option,是因为如果通过borrow_mut获取可变引用,则通过borrow获取的共享引用返回None,反之亦然。

RecCell的引用状态有三种,分别是非共享状态(Unshared)、共享状态(Shared)、独占状态,使用enum来表示。其中共享状态包含了一个引用计数器,类型为usize。

引用状态我们使用了上面刚完成的Cell进行包装,是因为需要使用内部可变性来改变状态。

下面来完成borrow和borrow_mut方法:

 1pub fn borrow(&self) -> Option<&T> {
 2    match self.state.get() {
 3        // 当前状态如果是非共享状态,则设置引用状态为共享状态
 4        RefState::Unshared => {
 5            self.state.set(RefState::Shared(1));
 6            Some(unsafe {&*self.value.get()})
 7        },
 8        // 当前状态如果是共享状态,则引用计数加1
 9        RefState::Shared(n) => {
10            self.state.set(RefState::Shared(n + 1));
11            Some(unsafe {&*self.value.get()})
12        },
13        // 当前状态如果是独占状态,则返回None
14        RefState::Exclusive => None,
15    }
16}
17
18pub fn borrow_mut(&self) -> Option<&mut T> {
19    // 引用状态既不是共享状态,也不是独占状态,才能设置为独占状态。
20    if let RefState::Unshared = self.state.get() {
21        self.state.set(RefState::Exclusive);
22        Some(unsafe {&mut *self.value.get()})
23    }else {
24        None
25    }
26}
现在有个问题,共享状态的引用计数只有增没有减,下面增加两个类型来完善RefCell
 1/**
 2 * 包装RefCell的共享引用struct
 3 */
 4pub struct Ref<'refcell, T> {
 5    refcell: &'refcell RefCell,
 6}
 7
 8impl Drop for Ref<'_, T> {
 9     // 超出作用域范围时,共享引用状态的变化
10    fn drop(&mut self) {
11        match self.refcell.state.get() {
12            RefState::Unshared | RefState::Exclusive => unreachable!(),
13            RefState::Shared(1) => {
14                self.refcell.state.set(RefState::Unshared);
15            },
16            RefState::Shared(n) => {
17                self.refcell.state.set(RefState::Shared(n - 1));
18            }
19        }
20    }
21}
22
23impl std::Deref for Ref<'_, T> {
24    type Target = T;
25
26    // 解引用时直接返回 T 的引用
27    fn deref(&self) -> &Self::Target {
28        unsafe {&*self.refcell.value.get()}
29    }
30}
 1/**
 2 * 包装RefCell的可变引用struct
 3 */
 4pub struct RefMut<'refcell, T> {
 5    refcell: &'refcell RefCell,
 6}
 7
 8impl Drop for RefMut<'_, T> {
 9     // 超出作用域范围时,独占引用状态的变化
10    fn drop(&mut self) {
11        match self.refcell.state.get() {
12            RefState::Unshared | RefState::Shared(_) => unreachable!(),
13            RefState::Exclusive => {
14                self.refcell.state.set(RefState::Unshared);
15            }
16        }
17    }
18}
19
20impl std::Deref for RefMut<'_, T> {
21    type Target = T;
22
23    // 解引用时直接返回 T 的引用
24    fn deref(&self) -> &Self::Target {
25        unsafe {&*self.refcell.value.get()}
26    }
27}
28
29impl std::DerefMut for RefMut<'_, T> {
30    // 解引用时直接返回 T 的可变引用
31    fn deref_mut(&mut self) -> &mut Self::Target {
32        unsafe {&mut *self.refcell.value.get()}
33    }
34}
RefCell的borrow和borrow_mut方法的返回值也需要做相应的修改:
 1pub fn borrow(&self) -> Option> {
 2    match self.state.get() {
 3        // 当前状态如果是非共享状态,则设置引用状态为共享状态
 4        RefState::Unshared => {
 5            self.state.set(RefState::Shared(1));
 6            Some(Ref{refcell: self})
 7        },
 8        // 当前状态如果是共享状态,则引用计数加1
 9        RefState::Shared(n) => {
10            self.state.set(RefState::Shared(n + 1));
11            Some(Ref{refcell: self})
12        },
13        // 当前状态如果是独占状态,则返回None
14        RefState::Exclusive => None,
15    }
16}
17
18pub fn borrow_mut(&self) -> Option> {
19    // 引用状态既不是共享状态,也不是独占状态,才能设置为独占状态。
20    if let RefState::Unshared = self.state.get() {
21        self.state.set(RefState::Exclusive);
22        Some(RefMut{refcell: self})
23    }else {
24        None
25    }
26}
通过我们自己实现的Cell,RefCell的过程,我们了解了智能指针为什么需要实现Drop、Deref和DerefMut特征及内部可变性的原理。  

 

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分