Rust_smart_pointer

智能指针

智能指针是一类数据结构,它们表现类似指针,但是也拥有额外的元数据和功能。

  • 引用计数reference counting )智能指针类型。这种指针允许数据有多个所有者,它会记录所有者的数量,当没有所有者时清理数据。
  • 在 Rust 中因为引用和借用,普通引用和智能指针的一个额外的区别是引用是一类 只借用数据的指针 ;相反,在大部分情况下,智能指针 拥有 它们指向的数据。

常用的智能指针:

  • Box<T>,用于在堆上分配值
  • Rc<T>,一个引用计数类型,其数据可以有多个所有者
  • Ref<T>RefMut<T>,通过 RefCell<T> 访问。( RefCell<T> 是一个在运行时而不是在编译时执行借用规则的类型)。

使用Box <T>指向堆上的数据

box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。

使用Box <T>在堆上储存数据

  • let b = Box::new(5);

Box允许创建递归类型

递归类型recursive type )的值可以拥有另一个同类型的值作为其自身的一部分。

  • cons list:
    • cons list 是一个来源于 Lisp 编程语言及其方言的数据结构,它由嵌套的列表组成。(类似于c++的广义表)
    • Box是指针,是可以知道大小的。
1
2
3
4
5
6
7
8
9
10
enum List {
Cons(i32, Box<List>),
Nil,
}

use crate::List::{Cons, Nil};

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

通过Deref trait将智能指针当作常规引用处理

  • 实现 Deref trait 允许我们重载 解引用运算符
  • 解引用
1
2
3
4
5
6
7
fn main() {
let x = 5;
let y = &x;

assert_eq!(5, x);
assert_eq!(5, *y);
}
  • 像引用一样使用Box <T>
1
2
3
4
5
6
7
fn main() {
let x = 5;
let y = Box::new(x);

assert_eq!(5, x);
assert_eq!(5, *y);
}
  • y 设置为一个指向 x 值拷贝的 Box<T> 实例,而不是指向 x 值的引用。

Rc <T>引用计数智能指针

如果某个值有零个引用,就代表没有任何有效引用并可以被清理。

  • 使用Rc <T>共享数据
1
2
3
4
5
6
7
8
9
10
11
12
13
enum List {
Cons(i32, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
**use std::rc::Rc;**

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}

也可以调用 a.clone() 而不是 Rc::clone(&a),不过在这里 Rust 的习惯是使用 Rc::clone

  • 克隆Rc <T>会增加引用计数
  1. Rc::clone
  • Rc::clone(&sun) 不会复制 Sun 实例,而是增加 Rc 的引用计数。
  • 所有行星共享同一个 Sun 实例。
  1. 引用计数
  • 每次调用 Rc::clone(&sun),引用计数加 1。
  • 每次调用 drop,引用计数减 1。
  1. drop 的作用
  • drop 用于手动释放所有权,减少引用计数。
  • 当引用计数为 0 时,Rc 会自动释放内存。

cow:

Cow 的核心思想是:

  • 如果数据是 只读的 ,直接 使用借用Borrowed),避免复制。
  • 如果数据 需要修改 ,则 复制数据并拥有所有权Owned)。

Cow 的常用方法

(1) to_mut:获取可变引用

如果 CowBorrowed,则复制数据并转换为 Owned;如果已经是 Owned,则直接返回可变引用。

1
2
3
4
let mut cow = Cow::Borrowed("hello");
let s = cow.to_mut(); // 复制数据并转换为 Owned
s.push_str(" world");
println!("{}", s); // 输出 "hello world"

(2) into_owned:获取所有权

Cow 转换为拥有所有权的数据。

1
2
3
let cow = Cow::Borrowed("hello");
let s: String = cow.into_owned(); // 转换为 String
println!("{}", s); // 输出 "hello"

(3) is_borrowedis_owned:检查状态

  • is_borrowed:检查是否是 Borrowed
  • is_owned:检查是否是 Owned
1
2
3
let cow = Cow::Borrowed("hello");
assert!(cow.is_borrowed());
assert!(!cow.is_owned());

使用Drop Trait运行清理代码

定义

1
2
3
pub trait Drop {
fn drop(&mut self);
}

drop方法在值离开作用域时自动调用

也可以手动调用drop()来清理代码

1
2
3
4
5
fn main() {
let x = MyStruct { name: String::from("x") };
drop(x); // 手动调用 drop
println!("x has been dropped");
}

Drop与所有权

  • 当一个值的所有权转移时,drop不会立即调用,只有在值离开作用域时才会调用
  • 如果值被移动(如传递给函数),drop会在新作用域结束时调用

Drop与Copy

二者不能同时实现

  • Copy trait 表示类型可以通过位复制来复制值,而 Drop 表示类型需要清理操作。
  • 如果类型实现了 Drop,Rust 会阻止你实现 Copy,因为复制可能会导致资源被多次释放。

RefCell <T>和内部可变模式

  • 内部可变性是rust中的一个设计模式,它允许你 即使在有不可变引用时也可以改变数据
  • 该模式在数据结构中使用 unsafe 代码来模糊rust通常的可变性和借用规则。不安全代码表明我们在手动检查这些规则而不是让编译器替我们检查。

通过 RefCell<T>在运行时检查借用规则

  • 不同于 Rc<T>RefCell<T> 代表其数据的唯一的所有权。

如下为选择 Box<T>Rc<T>RefCell<T> 的理由:

  • Rc<T> 允许相同数据 有多个所有者Box<T>RefCell<T> 有单一所有者。
  • Box<T> 允许在编译时执行不可变或可变借用检查;Rc<T>仅允许在编译时执行不可变借用检查;RefCell<T> 允许在运行时执行不可变或可变借用检查。
  • 因为 RefCell<T> 允许在运行时执行可变借用检查,所以我们可以在即便 RefCell<T> 自身是不可变的情况下修改其内部的值。

内部可变性:不可变值的可变借用

借用规则的一个推论是当有一个不可变值时,不能可变地借用它。

  • mock对象
    • mock 对象 是特定类型的 测试替身 ,它们记录测试过程中发生了什么以便可以断言操作是正确的。

主要方法

  • borrow():获取一个不可变引用(Ref<T>)。如果已经有可变引用,会 panic。
  • borrow_mut():获取一个可变引用(RefMut<T>)。如果已经有其他引用(可变或不可变),会 panic。
  • try_borrow()try_borrow_mut():与 borrow()borrow_mut() 类似,但不会 panic,而是返回 Result

Arc <T>Atomic Reference Counting (原子引用计数)

  1. 引用计数Arc<T> 会跟踪指向同一数据的引用数量。每次克隆 Arc<T> 时,引用计数会增加;当 Arc<T> 被丢弃时,引用计数会减少。当引用计数为 0 时,数据会被释放。
  2. 线程安全Arc<T> 是线程安全的,因为它的引用计数操作是原子的(atomic),这意味着多个线程可以安全地共享同一个 Arc<T>
  3. 不可变性Arc<T> 本身是不可变的。如果需要修改内部数据,通常需要与 Mutex<T>RwLock<T> 结合使用。

主要方法

  • new(value: T) -> Arc<T>:创建一个新的 Arc<T>
  • clone(&self) -> Arc<T>克隆 Arc<T>,增加引用计数。
  • strong_count(&self) -> usize:返回当前 强引用计数
  • downgrade(&self) -> Weak<T>:创建一个 弱引用Weak<T>),不会增加引用计数。
  • try_unwrap(this: Arc<T>) -> Result<T, Arc<T>>:如果引用计数为 1,则返回内部数据;否则返回 Arc<T>

Rust_smart_pointer
https://pqcu77.github.io/2025/02/19/Rust-smart-pointer/
作者
linqt
发布于
2025年2月19日
许可协议