智能指针是一种想指针一样的行为且具有其他能力的数据结构(Smart pointers, on the other hand, are data structures that not only act like a pointer but also have additional metadata and capabilities.)在 Rust 中,智能指针和引用最大的区别在于:智能指针可能会拥有这个数据。
智能指针是一个结构体,并且实现了 Deref(可以像引用一样使用智能指针) 和 Drop trait(实现当智能指针离开作用域后所需要做的是)。
这里主要介绍三种常见的智能指针:
Box<T>for allocating values on the heapRc<T>, a reference counting type that enables multiple ownershipRef<T>andRefMut<T>, accessed throughRefCell<T>, a type that enforces the borrowing rules at runtime instead of compile time.
Using Box<T> to Point to Data on the Heap
Box 主要用于在堆上分配数据,没有性能开销,也没有其他能力。
Box 主要适用于以下场景:
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
使用 Box<T> 在堆上分配空间
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
Enabling Recursive Types with Boxes
Recursive Types 就是一个值可以有另一个相同类型的值作为其自身的一部分。(a value can have another value of the same type as part of itself. )比如:链表。下面就使用 Box 创建一个链表:
enum List {
Cons(i32, Box<List>),
Nil
}
fn main() {
use List::Cons;
use List::Nil;
let list = Cons(3, Box::new(Cons(4, Box::new(Cons(5, Box::new(Nil))))));
}
如果不使用 Box,
enum List {
Cons(i32, List),
Nil
}
这在编译的时候是无法确定它所需要分配的空间大小,因为 List 的大小是一个递归,也就会无限循环下去。
Rc<T>
Rc is the reference counted smart pointer. 也就是说 Rc 是带有引用计数的智能指针,适用于一个值有多个所有者。比如下面这种情况,链表 b 和链表 c 同时拥有链表 a :
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
use List::{Cons, Nil};
let a = Cons(1, Rc::new(Cons(2, Rc::new(Nil))));
let tmp_a = Rc::new(a);
println!("{}", Rc::strong_count(&tmp_a));
let b = Cons(3, Rc::clone(&tmp_a));
println!("{}", Rc::strong_count(&tmp_a));
{
let c = Cons(4, Rc::clone(&tmp_a));
println!("{}", Rc::strong_count(&tmp_a));
}
println!("{}", Rc::strong_count(&tmp_a));
}
RefCell<T>
Rust 不允许不可变引用去改变数据的值,但是使用 RefCell 可以绕过编译时的检查,在运行时再进行 borrowing rules 的检查。
let a = RefCell::new(5);
println!("{}", a.borrow());
*a.borrow_mut() += 5;
println!("{}", a.borrow());
a 就是一个不可变引用,但是通过 *a.borrow_mut() 可以改变其拥有的值。
使用 RefCell 改造前面链表的例子, 使这个链表可以改变节点的值:
use std::rc::Rc;
use std::cell::RefCell;
use List::Cons;
use List::Nil;
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
fn main() {
let value = Rc::new(RefCell::new(1));
let a = Rc::new(Cons(value.clone(), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), a.clone());
let c = Cons(Rc::new(RefCell::new(4)), a.clone());
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
*value.borrow_mut() += 10;
println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
}
Deref && Drop Trait
Deref Trait
Implementing the Deref trait allows you to customize the behavior of the dereference operator, *(as opposed to &).
下面例子说明什么是解引用:
fn main() {
let x = 5;
let y = &x; // 引用
assert_eq!(5, x);
assert_eq!(5, *y); // 解引用
}
自己实现 Deref Trait
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
Implicit Deref Coercions with Functions and Methods
隐式解引用强制转换,这个概念通过下面的例子进行讲解:
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
fn hello(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
hello()的参数是 &str 类型,但是我们传入的是 &MyBox() 类型,&MyBox()会强制转换成&str,因为MyBox实现了Deref trait。Rust can turn &MyBox<String> into &String by calling deref. The standard library provides an implementation of Deref on String that returns a string slice.
Rust does deref coercion when it finds types and trait implementations in three cases:
- From &T to &U when T: Deref<Target=U>
- From &mut T to &mut U when T: DerefMut<Target=U>
- From &mut T to &U when T: Deref<Target=U>
Drop Trait
实现Drop trait 可以自定义数据释放的时候的行为。
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
使用 drop(c) 可以进行提前释放。
Weak Reference
Problem
Rust 语言虽然确保内存安全,但是内存泄露不在它保证的范围内。比如循环引用会导致内存泄露:
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial 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 after b creation = {}", Rc::strong_count(&a));
println!("b initial 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 after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}
b 被 drop 的时候,分配给 b 的空间并不会被释放,因为这时候 a 是持有 b 的引用,所以 b 的引用计数为1,它分配的空间不会被清理。
Solution
通过使用 Weak<T> 解决这个问题,Weak 也是在 std::rc 这个库中,是 Rc 的降级。只要强引用计数为0,不管弱引用计数为几,堆上的空间都会被释放。
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Weak<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Weak<List>>> {
match self {
Cons(_, x) => Some(x),
Nil => None,
}
}
}
use List::{Cons, Nil};
fn main() {
let a = Rc::new(Cons(1, RefCell::new(Weak::new())));
let b = Rc::new(Cons(2, RefCell::new(Weak::new())));
if let Some(x) = List::tail(&b) {
*x.borrow_mut() = Rc::downgrade(&a);
}
if let Some(x) = a.tail() {
*x.borrow_mut() = Rc::downgrade(&b);
}
}