【Rust 中级教程】 12 共享所有权

简介: 【Rust 中级教程】 12 共享所有权

0x00 开篇


所有权的概念非常苛刻,要求每个值有且仅有一个所有者。但是 Rust 也提供了相应的解决方法——共享所有权和借用。本篇文章将介绍共享所有权的一些概念。本篇文章存在一些还未介绍到的引用概念,如果您对引用概念不是很了解,可以先略过此文章。本篇文章的阅读时间大约 12 分钟。


0x01 引用计数


前面介绍 转移 的概念时,我对比了 pythonC++的对赋值概念的内存模型。当时 python 使用的方法是引用计数,其实共享所有权的原理同样是引用计数。Rust 提供了 Rc (Reference Count) 和 Arc (Atomic Reference Count) 两种类型来实现引用计数。


Rc 与 Arc 的用法完全相同,仅仅是应用场景不同,Arc 可以在线程中安全共享,Rc 则并不关注线程安全。接下来的演示我将仅以 Rc 为例。


0x02 Rc 的使用


使用 Rc 可以让一个值拥有多个所有者,每当值增加一个所有者,引用计数就会增加 1。当引用计数为 0 时,变量的内存将会被释放。


Rc 的使用还是很简单的,下面来看下代码吧。

use std::rc::Rc;
fn main() {
    // 创建一个字符串 rust
    let a: Rc<String> = Rc::new(String::from("rust"));
    // 调用 clone 方法,使指向字符串的引用计数 +1
    let b = a.clone();
    // Rc::clone(&a) 等价于 a.clone()
    let c = Rc::clone(&a);
    // 输出3个变量指向字符串的地址
    println!("{:p}", a.as_ptr());
    println!("{:p}", b.as_ptr());
    println!("{:p}", c.as_ptr());
    // 查看引用计数
    println!("引用计数 {} ", Rc::strong_count(&a));
    // 下面的代码只做了解  
    println!("弱引用计数 {} ", Rc::weak_count(&a));
}
// 运行结果
// a 的地址: 0x28f97c2ac10
// b 的地址: 0x28f97c2ac10
// c 的地址: 0x28f97c2ac10
// 引用计数 3 
// 弱引用计数 0

我们先创建一个拥有共享所有权的字符串 a = "rust" , 使用克隆方法克隆 a 并与 bc 变量绑定,Rc::cloneclone 方法是等价的,使用哪个都可以。最后输出 3 个变量字符串的地址。以及 a 的引用计数。从结果中可以看到地址都是一样的。并且我们还可以直接使用变量 a 来调用 String 的方法(下面是示例代码)。

use std::rc::Rc;
fn main() {
    // 创建一个字符串 rust
    let a: Rc<String> = Rc::new(String::from("rust"));
    // 使用 String 中的方法。
    if a.contains("ru") {
        println!("true");
    }
}
// 运行结果
// true

其实 Rc<T>持有一个指针(接下来的文章会介绍),指向堆空间的 T 值,同时该值还会有一个引用计数。使用 clone 方法只会增加引用计数,并不会复制值。但是使用 Rc 指针引用的值不能被修改。


0x03 了解 Rc 源码


我们再来看下源码。Rc 其实就是一个结构体,有两个字段。其中一个是 NonNull<RcBox>PhantomDataPhantomData 这个空结构体很有趣,字面意识是“虚幻数据”,它不占内存空间,主要用于帮助编译器做检查。另外一个就是 NonNull 持有一个指针,指向 RcBox。重点来看 RcBox, 它有三个字段:strong 就是引用计数了,weak 则就是弱引用计数,value 才是真正的值。


// rc.rs
pub struct Rc<T: ?Sized> {
    ptr: NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
}
// rc.rs
struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}
// non_null.rs
pub struct NonNull<T: ?Sized> {
    pointer: *const T,
}
// marker.rs
pub struct PhantomData<T: ?Sized>;
// rc.rs
impl<T: ?Sized> Rc<T> {
    // ...
    pub fn strong_count(this: &Self) -> usize {
        this.inner().strong()
    }
    pub fn weak_count(this: &Self) -> usize {
        this.inner().weak() - 1
    }
    // ...
}

PS:有关 Rust 弱引用的相关知识暂不介绍,暂做了解即可。印象中记得,弱引用好像还是一个面试常问的问题,哈哈。


0x04 Rc 的内存布局


我们通过断点再次运行代码,来看下内存。


0a2653c851af460fa595bd959398a8f1.png


首先,我们拿到了 a 的地址 0x0000007dbf6ff7f0 , b 的地址0x0000007dbf6ff810c 的地址0x0000007dbf6ff810。可以看到三个变量持有的数据是一样的, 都是0x000001e5c9f7df40。通过第三小节我们也了解到 Rc 是一个结构体,但是 PhantomData 是一个虚幻数据,不占内存空间,所以这个地址应该指向的就是 RcBox 了。接下来通过这个地址找到数据,验证下我们的猜想。

2d65d23f6d4748949b924e4057485923.png


找到这个地址,红色框8个字节是 strong字段,黄色框8个字节是 weak 字段,紫色框的24个字段是 value 字段,这里的 value是 String 类型,String 其实就是封装了向量的结构体,向量的内存布局则分为3部分:数据的指针,长度和容量。如果对向量和字符串内存布局不是很熟悉的读者,可以再回顾下前面的文章。从内存里可以得知 strong的值是 3,weak 的值是 1。又有读者可能要问了,上面输出的 weak_count 是 0,为什么这里是 1 呢?第三节的源码中可以得知 weak_count方法返回的值是 weak - 1,所以这里是1。value的长度是 4,容量也是 4,数据指针是 0x000001e5c9f7be50,我们再看下,这个地址是不是 rust 呢。


6de278e6d6694ce5bb08e7e842b7e74b.png


毫无疑问,肯定是的。最后,画个简单的内存布局吧(如下图)。


8ec4f2997fb246878c34ecd6d122b7c6.png


0x05 小结


本篇文章介绍的是共享所有权,主要了解了 Rc,但是 Rc 有个很明显的特点就是不可修改。Rust 为了安全,设定了既然要共享,那就不可改变这个前提。如果可修改,那就可能出现两个变量互相指向对方,这会导致引用计数永远不会为 0,使得两个值永远都不会释放,最终导致内存泄漏。但是 Rust 也提供了内部修改能力,这部分可能会在高级篇章介绍。文章也提到了弱引用,这个可以先暂时作为了解即可。本文也提到了 Arc,使用方法也大同小异,如果没有多线程的场景,还是建议使用 Rc。

相关文章
|
6月前
|
存储 Rust
【Rust】——所有权规则、内存分配
【Rust】——所有权规则、内存分配
|
3月前
|
存储 Rust 安全
30天拿下Rust之所有权
在编程语言的世界中,Rust凭借其独特的所有权机制脱颖而出,为开发者提供了一种新颖而强大的工具来防止内存错误。这一特性不仅确保了代码的安全性,还极大地提升了程序的性能。在Rust中,所有权是一种编译时检查机制,用于追踪哪些内存或资源何时可以被释放。每当一个变量被赋予一个值(比如:字符串、数组或文件句柄)时,Rust会确定这个变量是否“拥有”这个值,拥有资源的变量负责在适当的时候释放这些资源。
38 5
|
4月前
|
存储 Rust 安全
【Rust学习】04_所有权
所有权是 Rust 最独特的特性,对语言的其余部分有着深远的影响。它使 Rust 能够在不需要垃圾收集器的情况下保证内存安全,因此了解所有权的运作方式非常重要。在本章中,我们将讨论所有权以及几个相关功能:借用、切片以及 Rust 如何在内存中布局数据。
26 1
|
4月前
|
Rust 安全 程序员
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
|
6月前
|
存储 Rust 安全
Rust 笔记:Rust 语言中的 所有权 与 生命周期
Rust 笔记:Rust 语言中的 所有权 与 生命周期
184 0
|
5月前
|
Rust 安全 开发者
Rust引用、借用和所有权详解
Rust引用、借用和所有权详解
|
6月前
|
Rust 算法 安全
【Rust中的所有权系统深入解析】A Deep Dive into Rust‘s Ownership System
【Rust中的所有权系统深入解析】A Deep Dive into Rust‘s Ownership System
98 0
|
6月前
|
Rust 安全 编译器
深入Rust的所有权系统:理解变量的所有权
本文详细探讨了Rust编程语言中所有权系统的核心概念,包括变量的所有权、生命周期、借用规则和内存安全。通过理解这些概念,我们能够编写出更加高效、安全和可维护的Rust代码。
|
6月前
|
Rust 编译器
【Rust】——函数(所有权)以及借用或引用
【Rust】——函数(所有权)以及借用或引用
|
6月前
|
存储 缓存 Rust
【Rust】——所有权:Stack(栈内存)vs Heap(堆内存)(重点)
【Rust】——所有权:Stack(栈内存)vs Heap(堆内存)(重点)