Rust能力养成系列之(41):内存管理:引用计数智能指针(上)

前言

Rust的所有权规则(ownership rule)允许在给定的范围内一次只存在一个所有者。但是,在某些情况下,需要与多个变量共享类型。例如,在GUI库中,每个子小部件(child widget)都需要有一个对其父容器小部件(parent container widget)的引用,以便根据来自用户发起的的调整大小事件(resize event)对子小部件进行布局。虽然生命周期允许开发者通过将父节点存储为&'a parent来从子节点引用父节点,但它通常受到值的生命周期参数a的限制。一旦范围结束,引用也就无效了。在这种情况下,我们需要更灵活的方法,这就涉及使用引用计数类型(reference counting types)。这些智能指针类型能够提供程序中值的共享所有权。

引用计数类型能够做到在细粒度级别上进行垃圾收集。在这种方法中,智能指针类型允许对封装的值有多个引用。在内部,智能指针保存它给出的引用数量的计数,并使用一个引用计数器(此处为refcount)活动,这里是一个整数值。当引用封装的智能指针值的变量超出作用域时,refcount值递减。一旦对该对象的所有引用都消失了,并且refcount达到0,就会释放该值。这就是引用计数指针通常的工作方式。

Rust为我们提供了两种参考计数指针类型:

  • Rc<T>:主要用于单线程环境( single threaded environments)
  • Arc<T>用于多线程环境(multi-threaded environments)

我们会在这一章节里探索单线程情况,而后在第8章“并发性”中讨论下多线程的情况。

 

Rc<T>

当我们与Rc类型交互时,它内部会发生以下变化:

  • 当通过调用Clone()获取一个新的Rc共享引用时,Rc会增加它的内部引用计数。Rc在内部为其引用计数使用单元格类型( Cell type )
  • 当引用超出作用域时,将其减1
  • 当所有共享引用超出作用域时,引用计数变为零。此时,对Rc的最后一个drop调用将执行它的重新分配

使用引用计数容器为实现提供了更大的灵活性:我们可以像分发新副本一样分发值的副本,而不必精确跟踪引用何时超出了作用域。当然,这并不意味着我们可以以可变方式为内部值添加别名。

Rc<T>主要通过两种方法来使用:

  • 利用静态方法Rc::new创建一个新的引用计数容器
  • 利用Clone方法增加强引用计数,并分发一个新的Rc<T>

Rc内部保留两种引用: strong (Rc<T>) 和weak (Weak<T>).。两者都记录每种类型的引用已分发的数量,但只有当强引用计数达到0时,这些值才会被回收。这样做的动机是,数据结构的实现可能需要多次指向相同的东西。例如,树的实现可能同时具有对子节点和父节点的引用,但是对每个引用都递增计数器数值是不正确的,并会导致引用循环。下面的图就说明了引用循环的情况:

如上面图表所见,我们有两个变量var1和var2,它们引用两个资源Obj1和Obj2。除此之外,Obj1也有一个对Obj2的引用Obj2也有一个对Obj1的引用。当var1和var2超出范围时,Obj1和Obj2的引用计数都为1。它们不会被释放,因为它们仍然相互关联。

可以使用弱引用(weak reference)来打破引用循环。另一个例子是,链表可以通过以下方式实现:通过对下一项和上一项的引用计数来维护链接。更好的方法是对一个方向使用强引用,对另一个方向使用弱引用。

让我们看看这是怎么做的。下面是一个可能最不实用但最适合学习数据结构的最小实现,即单链表:

// linked_list.rs

use std::rc::Rc; 

#[derive(Debug)] 
struct LinkedList<T> { 
    head: Option<Rc<Node<T>>> 
} 

#[derive(Debug)] 
struct Node<T> { 
    next: Option<Rc<Node<T>>>, 
    data: T 
} 

impl<T> LinkedList<T> { 
    fn new() -> Self { 
        LinkedList { head: None } 
    } 

    fn append(&self, data: T) -> Self { 
        LinkedList { 
            head: Some(Rc::new(Node { 
                data: data, 
                next: self.head.clone() 
            })) 
        } 
    } 
} 

fn main() { 
    let list_of_nums = LinkedList::new().append(1).append(2); 
    println!("nums: {:?}", list_of_nums); 

    let list_of_strs = LinkedList::new().append("foo").append("bar"); 
    println!("strs: {:?}", list_of_strs); 
}

看一下,这个链表由两个结构体组成:LinkedList提供对列表第一个元素和列表的公共API的引用,Node包含实际的元素。请注意我们是如何使用Rc和在每个追加(append)上克隆下一个数据指针的。那么来看看在追加情况下发生了什么:

  • LinkedList::new()给我们一个新的列表,head:None
  • 我们向列表追加1。head作为数据的节点,现在是包含1,下一个是前一个的head:None
  • 我们向列表追加2。head作为数据的节点,现在是包含2,下一个是前一个的head:1

debug的输出println!证实了这一点:

不难看出,这体现出这个结构的一种函数式形式;每个append的工作方式都是在head(头部)添加数据,这意味着我们不必使用引用,而实际的列表引用可以保持不变。如果我们想保持这个简单的结构,但仍然有一个双链表,那么我们实际上必须改变现有的结构。

 

结语

那么如何做相应的改变呢,姑且作为这一篇的作业,读者可以想一下,我们下篇再说。

 

主要参考和建议读者进一步阅读的文献

https://doc.rust-lang.org/book

深入浅出 Rust,2018,范长春

Rust编程之道,2019, 张汉东

The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

Beginning Rust ,2018,Carlo Milanesi

Rust Cookbook,2017,Vigneshwer Dhinakaran

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
rust 数据结构与算法,英文版,epub格式 Chapter 1, Hello Rust!, gives a short recap of the Rust programming language and what changed in the 2018 edition. Chapter 2, Cargo and Crates, discusses Rust's cargo build tool. We will explore the configuration as well as the build process and modularization options. Chapter 3, Storing Efficiently, looks at how in Rust, knowing where values are stored is not only important for performance, but also important for understanding error messages and the language in general. In this chapter, we think about stack and heap memory. Chapter 4, Lists, Lists, and More Lists, covers the first data structures: lists. Using several examples, this chapter goes into variations of sequential data structures and their implementations. Chapter 5, Robust Trees, continues our journey through popular data structures: trees are next on the list. In several detailed examples, we explore the inner workings of these efficient designs and how they improve application performance considerably. Chapter 6, Exploring Maps and Sets, explores the most popular key-value stores: maps. In this chapter, techniques surrounding hash maps; hashing; and their close relative, the set; are described in detail. Chapter 1, Hello Rust!, gives a short recap of the Rust programming language and what changed in the 2018 edition. Chapter 2, Cargo and Crates, discusses Rust's cargo build tool. We will explore the configuration as well as the build process and modularization options. Chapter 3, Storing Efficiently, looks at how in Rust, knowing where values are stored is not only important for performance, but also important for understanding error messages and the language in general. In this chapter, we think about stack and heap memory. Chapter 4, Lists, Lists, and More Lists, covers the first data structures: lists. Using several examples, this chapter goes into variations of sequential data structures and their implementations. Chapter 5, Robust Trees, continues our journey through popular data structures: trees are next on the list. In several detailed examples, we explore the inner workings of these efficient designs and how they improve application performance considerably. Chapter 6, Exploring Maps and Sets, explores the most popular key-value stores: maps. In this chapter, techniques surrounding hash maps; hashing; and their close relative, the set; are described in detail.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值