Rust 学习笔记——智能指针

本文介绍了Rust中的三种智能指针:Box用于动态内存分配,Rc实现引用计数防止循环引用,RefCell则允许运行时检查借用规则。通过实例演示了如何在链表和内存管理中运用这些智能指针来保证内存安全和所有权管理。
摘要由CSDN通过智能技术生成

https://doc.rust-lang.org/book/ch15-00-smart-pointers.html

智能指针是一种想指针一样的行为且具有其他能力的数据结构(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 heap
  • Rc<T>, a reference counting type that enables multiple ownership
  • Ref<T> and RefMut<T>, accessed through RefCell<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);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值