rust学习十五.4、Rc和RefCell指针

一、前言

作为初学者,在只学习了前面几个章节的时候,我以为rust的所有权规则和它说的是一样的。

但实际上,rust发明人并没有遵循这个规则。按照我的想法,应该是因为如果坚持那样可能编写某些代码会太痛苦,甚至可能根本无法实现。

可能根本无法实现这是一个没有去证实的猜想。 不过,我很坚信:如果还是坚持所有权那一套,大概写某些代码会很痛苦的

在概览了书籍的15章节之后,这是我得出的初步看法。

Rc和RefCell指针就是违背所有权看法的两个指针:

Rc-一份数据可以有多个所有者

RefCell--允许内部修改数据,但又让你外部看起来不可修改(可以通过编译)

无论如何,虽然别扭,这些代价还是可以忍受的,考虑到C++的问题。

二、定义

Rc  -- Reference counter

Rc即引用计数指针,每此增加一个引用,计数+1,反之脱离范围后,引用计数自动减少。当引用计数为0的时候,可以删除这个指针。

RefCell -- Reference Cell

RefCel即引用蜂巢指针,或者是引用指针蜂巢/引用隔间指针,用于封装对内部数据的可变引用。

三、Rc说明及其示例

总之,利用Rc指针,我们可以实现多个变量共享一个Rc指针,实现一些特别的目的。

但需要注意的是:Rc指针只能用于单线程,或者说不是多线程安全

3.1Rc部分源码

a.定义

#[cfg_attr(not(test), rustc_diagnostic_item = "Rc")]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_insignificant_dtor]
pub struct Rc<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
> {
    ptr: NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
    alloc: A,
}

 

b.new方法

impl<T> Rc<T> {
    #[cfg(not(no_global_oom_handling))]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn new(value: T) -> Rc<T> {
        // There is an implicit weak pointer owned by all the strong
        // pointers, which ensures that the weak destructor never frees
        // the allocation while the strong destructor is running, even
        // if the weak pointer is stored inside the strong one.
        unsafe {
            Self::from_inner(
                Box::leak(Box::new(RcBox { strong: Cell::new(1), weak: Cell::new(1), value }))
                    .into(),
            )
        }
    }
}

 

这个new中有许多不认识的内容,需要认真研究下。

但可以看出,这个指针实际至少包含一个RcBox,包含了三个部分:strong(强引用),weak(弱引用),value实际的值.

Cell大体上可以看作只有一个值的结构体。

 

c.一个重要的函数clone

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized, A: Allocator + Clone> Clone for Rc<T, A> {
    #[inline]
    fn clone(&self) -> Self {
        unsafe {
            self.inner().inc_strong();
            Self::from_inner_in(self.ptr, self.alloc.clone())
        }
    }
}

 

self.inner().inc_strong()大体就是增加计数的意思。

这个clone不是一般印象上深度克隆或者浅拷贝之类,功能可以大约归纳为:引用计数+1

大体上,源码中还有许多不认识,不理解,不过不妨碍大体的理解。

更多的内核代码无法给出,因为有些实现,例如Box是通过编译器处理的。

 

3.2 示例

这是模仿书本的示例,两个结构共享一个字符串Rc指针

use std::rc::Rc;
fn main() {
    rc_test();
}

#[derive(Debug)]
struct student{
    name:Rc<String>,
    age:i32,
}

fn  rc_test(){
    let name=Rc::new(String::from("张三"));
    let stu1=student{name:Rc::clone(&name),age:20};
    println!("{:?}",stu1);
    println!("{} 引用次数:{}",name,Rc::strong_count(&name));
    let stu2=student{name:Rc::clone(&name),age:21};
    println!("{:?}",stu2);
    println!("{} 引用次数:{}",name,Rc::strong_count(&name));
}

 

 

 

四、RefCell说明及其示例

RefCell<T> 提供了内部可变性,这意味着你可以在不可变上下文中修改数据,而不需要实现 Mutex 或其他同步机制。

它通过运行时借用检查来实现这一点,而不是编译时检查。然而,RefCell<T> 也不是线程安全的;它不允许同时从多个线程访问数据

4.1、部分源码

#[cfg_attr(not(test), rustc_diagnostic_item = "RefCell")]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct RefCell<T: ?Sized> {
    borrow: Cell<BorrowFlag>,
    // Stores the location of the earliest currently active borrow.
    // This gets updated whenever we go from having zero borrows
    // to having a single borrow. When a borrow occurs, this gets included
    // in the generated `BorrowError`/`BorrowMutError`
    #[cfg(feature = "debug_refcell")]
    borrowed_at: Cell<Option<&'static crate::panic::Location<'static>>>,
    value: UnsafeCell<T>,
}

 

除了value,还有两个属性:borrow,borrowed_at

这两个属性都是基于Cell的,Cell定义如下:

#[stable(feature = "rust1", since = "1.0.0")]
#[repr(transparent)]
#[rustc_pub_transparent]
pub struct Cell<T: ?Sized> {
    value: UnsafeCell<T>,
}

 

Cell根据注解,是rust内部可变能力的原生实现

borrow主要标记当前变量借用情况,例如是可变借用还是不可变借用之类的。

borrow_at,如其名,主要标记在代码中,什么地方发生了借用

这两个标记辅助实现借用检查。

 

RefCell的两个常用函数:borrow,borrow_mut,分别表示不可变借用和可变借用

我们主要看下borrow_mut

   #[stable(feature = "rust1", since = "1.0.0")]
    #[inline]
    #[track_caller]
    pub fn borrow_mut(&self) -> RefMut<'_, T> {
        match self.try_borrow_mut() {
            Ok(b) => b,
            Err(err) => panic_already_borrowed(err),
        }
    }

该方法返回一个RefMut<'_,T> ,一个可变借用。 如果失败,会导致程序中止。

 

4.2 、示例

#[derive(Debug)]
struct Book{
    name:String,
    author:String,
    content:RefCell<String>,
}

impl Book{
    fn append_content(&self,con:&str){
        self.content.borrow_mut().push_str(con);
    }
}
fn main(){
    let book = Book{
        name:"Rust编程之道".to_string(),
        author:"lzfHope".to_string(),
        content:RefCell::new("努力学习,战胜AI".to_string()),
    };
    book.append_content(",这是新时代的程序员应有的素养。");
    println!("{:?}",book);
}

 

 

五、小结

rust必须屈服于软件工程:即为工程师提供足够的便利性,同时也必须实现一些特定的目标

所以它们推出了Rc和RefCell指针,以便可以实现在其它语言中可以轻易实现的功能。

在其它语言中,希望多个多项共有一个值,或者修改特定对象的内部属性,都是轻而易举的,但是在rust中,变得困难重重。

有了Rc就可以多个共有一个值,有了RefCell就允许修改不可变变量的内部某个值。 Rust为了实现它的目标不得不做出一些牺牲。

 

posted @ 2025-01-05 10:57  正在战斗中  阅读(59)  评论(0编辑  收藏  举报