rust智能指针

时间:2024-11-15 21:17:28

文章目录

  • rust中引用和智能指针的区别
    • 引用 (&T 和 &mut T)
    • 智能指针
    • 引用和智能指针的区别总结
    • 什么时候使用引用,什么时候使用智能指针?
  • 使用 Box\ 指向堆上的数据
    • 使用场景
    • Example
  • Deref特征
    • 自定义智能指针
    • 隐式类型转换deref coercions
      • Rust 在发现类型和 trait 的实现满足以下三种情况时会进行解引用强制转换(隐式类型转换deref coercions)
  • Drop特征
    • 自动清理
    • 手动清理
  • Rc 引用计数智能指针
  • RefCell 和内部可变性模式---单一线程
    • 问题代码
    • 使用RefCell解决
    • 多次调用borrow_mut(),只会在运行时报错
    • 结合 Rc\ 和 RefCell\ 来拥有多个可变数据所有者
  • 循环引用可能导致的内存泄露问题
  • 参考

rust中引用和智能指针的区别

在 Rust 中,引用(&T 和 &mut T)和 智能指针(如 Box<T>、Rc<T>、Arc<T> 等)有很大的区别,虽然它们都与内存管理和所有权相关,但它们在行为、用途和生命周期等方面存在不同.

引用 (&T 和 &mut T)

引用是 Rust 中的一种借用机制,它允许你通过引用访问数据而不获取数据的所有权。

  • 不可变引用:&T 是一个不可变引用,表示对数据的只读访问。
  • 可变引用:&mut T 是一个可变引用,表示对数据的可修改访问。

特点:

  • 借用:引用是借用数据的方式,意味着你不会拥有数据。所有权仍然属于原始变量。
  • 生命周期:引用有生命周期,它的有效期由 Rust 的借用检查器保证。例如,在可变引用的情况下,在同一时刻只能有一个可变引用,或者多个不可变引用,但不能同时存在。
  • 不负责内存管理:引用不会管理数据的内存,它只是在生命周期内提供对数据的访问。
  • 不可移动:引用不能移动数据,它只是数据的一个指针或访问者。
fn main() {
    let x = 5;
    let y = &x; // 不可变引用
    println!("{}", y); // 可以读取 x 的值

    let mut z = 10;
    let w = &mut z; // 可变引用
    *w += 5; // 修改 z 的值
    println!("{}", z); // 输出 15
}

智能指针

智能指针是实现了 Deref 或 Drop 特性的类型,通常会像指针一样使用,但它们还负责管理数据的内存和资源。智能指针通常用于内存管理、引用计数、动态分配等场景。

常见的智能指针:

  • Box<T>:用于将数据堆分配,并提供对数据的所有权。Box 是一种 拥有数据的指针,当它超出作用域时,数据会被自动清理。
  • Rc<T> 和 Arc<T>:是引用计数类型,允许多个所有者共享同一数据。Rc 是单线程版本,而 Arc 是线程安全的版本。
  • RefCell<T> 和 Mutex<T>:提供在运行时进行可变借用的智能指针,RefCell 是内存中的一个可变借用,Mutex 用于多线程同步。

特点:

  • 所有权:智能指针负责数据的所有权,并且通常提供额外的内存管理功能(如引用计数、自动销毁等)。
  • 内存管理:智能指针通常会自动释放内存,例如 Box<T> 会在超出作用域时自动释放内存,而 Rc<T> 会通过引用计数来决定何时释放数据。
  • 动态分配:智能指针通常会在堆上分配数据,而引用指向的是栈上的数据或已分配堆内存。
  • 更多功能:智能指针通常支持 Deref 和 Drop 等特性,可以提供更多的控制功能,如实现类似于解引用的行为。
fn main() {
    let x = Box::new(5); // Box 智能指针
    println!("{}", x); // 解引用 Box 中的值

    let y = Rc::new(5); // Rc 智能指针
    let z = Rc::clone(&y); // 引用计数增加
    println!("{}", y); // Rc 会在内部处理内存管理
}

引用和智能指针的区别总结

特性 引用 (&T 和 &mut T) 智能指针(如 Box、Rc)
所有权 引用没有所有权,只是借用数据 智能指针拥有数据的所有权
内存管理 不负责内存管理 负责内存管理,通常有自动销毁机制
生命周期 有明确的生命周期 生命周期取决于智能指针本身
数据存储位置 引用通常指向栈或堆上的数据 智能指针通常分配堆上的内存
操作 只提供对数据的访问 提供更多的操作功能,如引用计数、自动清理等
线程安全 不保证线程安全 一些智能指针(如 Arc)是线程安全的

什么时候使用引用,什么时候使用智能指针?

使用引用:当你只需要借用数据并在函数或作用域内使用它时,引用是更轻量的选择。它不会引入所有权的转移,也不会对内存管理造成额外的开销。
使用智能指针:当你需要在堆上分配数据、动态管理内存、或者需要多个所有者时,智能指针(如 Box<T>、Rc<T>、Arc<T>)是更好的选择。智能指针会自动管理内存,并且可以处理更复杂的所有权和生命周期问题。

总的来说,引用和智能指针各有其独特的用途,根据你的需求来选择使用哪种方式。

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

智能指针就是实现Deref和Drop特征的结构体

使用场景

它们多用于如下场景:

当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候

Example

enum List {
    Cons(i32, List),
    Nil,
}

Cons数据结构类似如下所示
在这里插入图片描述

// 编译时无法确定其空间大小
// enum List {
//     Cons(i32, List),
//     Nil,
// }
// 使用智能指针改造
enum List {
    Cons(i32, Box<List>),
    Nil,
}

// rust编译时可以确定其大小
//它可以检查每一个成员并发现 Message::Quit 并不需要任何空间,Message::Move 需要足够储存两个 i32 值的空间,依此类推。因为只会使用一个成员,
//所以 Message 值需要的最大空间是存储其最大成员所需的空间大小
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

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

Deref特征

像普通引用一样的使用智能指针

fn main() {
    let x = 5;
    let y = &x; //y中存储的是x的引用/地址

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

用Box包下,效果时一样的。说明就是解引用操作符就是实现Deref特质

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

自定义智能指针

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

// Deref trait,由标准库提供,要求实现名为 deref 的方法,其借用 self 并返回一个内部数据的引用。
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

隐式类型转换deref coercions

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let m = MyBox::new(String::from("Rust"));

    let t1 = &m;
    let t2 = t1.deref();
    let t3 = t2.deref();

    // &mg干的事情:&Mybox<String> -> &String->&str,类似上面的操作分布流程
    // 等价写法:
    hello(&(*m)[..]); //复杂写法
    hello(&m);
}

fn hello(name: &str) {
    println!("Hello, {}!", name);
}

另外,对可变引用实现deref,则需要实现deref mut特征

Rust 在发现类型和 trait 的实现满足以下三种情况时会进行解引用强制转换(隐式类型转换deref coercions)

类似于使用 Deref trait 重载不可变引用的 * 运算符,Rust 提供了 DerefMut trait 用于重载可变引用的 * 运算符。

Rust 在发现类型和 trait 的实现满足以下三种情况时会进行解引用强制转换

当 T: Deref<Target=U> :从 &T 到 &U。
当 T: DerefMut<Target=U> :从 &mut T 到 &mut U。
当 T: Deref<Target=U> :从 &mut T 到 &U。

前两种情况除了可变性之外是相同的:第一种情况表明如果有一个 &T,而 T 实现了返回 U 类型的 Deref,则可以直接得到 &U。第二种情况表明对于可变引用也有着相同的行为。

第三种情况有些微妙:Rust 也会将可变引用强转为不可变引用,但是反之是 不可能 的,因为不可变引用永远也不能强转为可变引用。因为根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译)。将一个可变引用转换为不可变引用永远也不会打破借用规则。将不可变引用转换为可变引用则需要数据只能有一个不可变引用,而借用规则无法保证这一点。

因此,Rust 无法假设将不可变引用转换为可变引用是可能的。

Drop特征

自动清理

struct CustomSmartPointer {
    data: String,
}

// drop 特征式自动引入的
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.");
}

编译及运行

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: unused variable: `c`
  --> src/main.rs:13:9
   |
13 |     let c = CustomSmartPointer {
   |         ^ help: if this is intentional, prefix it with an underscore: `_c`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `d`
  --> src/main.rs:16:9
   |
16 |     let d = CustomSmartPointer {
   |         ^ help: if this is intentional, prefix it with an underscore: `_d`

warning: `smartPtr` (bin "smartPtr") generated 2 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.24s
     Running `target/debug/smartPtr`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

手动清理

struct CustomSmartPointer {
    data: String,
}

// drop 特征式自动引入的
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"),
    };
    drop(c); //手动调用drop函数,必须这么调用,全局的drop()函数式rust提供的
    println!("CustomSmartPointers created.");
}

编译及运行

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: unused variable: `d`
  --> src/main.rs:16:9
   |
16 |     let d = CustomSmartPointer {
   |         ^ help: if this is intentional, prefix it with an underscore: `_d`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: `smartPtr` (bin "smartPtr") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.67s
     Running `target/debug/smartPtr`
Dropping CustomSmartPointer with data `my stuff`!
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!

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

引用计数的思想和C++shared_ptr很像

不能使用Box智能指针的原因见:链接

Rc<T> 引用计数智能指针只能用于读取数据,不能用于修改数据

example:
列表 a 包含 5 之后是 10,之后是另两个列表:b 从 3 开始而 c 从 4 开始。b 和 c 会接上包含 5 和 10 的列表 a。换句话说,这两个列表会尝试共享第一个列表所包含的 5 和 10。
在这里插入图片描述

use crate::List::{Cons, Nil};
use std::rc::Rc;
enum List {
    Cons(i32, Rc<List>),
    Nil,
}
fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));

    let b = Cons(3, a.clone()); //等价于let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

打印引用计数

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)))));
    println!("count after creating a = {}", Rc::strong_count(&a)); //打印引用计数Rc::strong_count(&a),后续还有Rc::weak_count
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}


测试

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: unused variable: `b`
  --> src/main.rs:12:9
   |
12 |     let b = Cons(3, Rc::clone(&a));
   |         ^ help: if this is intentional, prefix it with an underscore: `_b`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `c`
  --> src/main.rs:15:13
   |
15 |         let c = Cons(4, Rc::clone(&a));
   |             ^ help: if this is intentional, prefix it with an underscore: `_c`

warning: fields `0` and `1` are never read
 --> src/main.rs:2:10
  |
2 |     Cons(i32, Rc<List>),
  |     ---- ^^^  ^^^^^^^^
  |     |
  |     fields in this variant
  |
  = note: `#[warn(dead_code)]` on by default
help: consider changing the fields to be of unit type to suppress this warning while preserving the field numbering, or remove the fields
  |
2 |     Cons((), ()),
  |          ~~  ~~

warning: `smartPtr` (bin "smartPtr") generated 3 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.64s
     Running `target/debug/smartPtr`
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2

RefCell<T> 和内部可变性模式—单一线程

RefCell<T> 在运行时检查借用规则,Box指针是编译时检查借用规则的

RefCell<T>也要求不能同时拥有两个可变引用

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

  • Rc<T> 允许相同数据有多个所有者;Box<T> 和 RefCell<T> 有单一所有者。
  • Box<T> 允许在编译时执行不可变或可变借用检查(如果Box智能指针指向的值是可变的,那么Box智能指针必须是可变的);Rc<T>仅允许在编译时执行不可变借用检查;RefCell<T> 允许在运行时执行不可变或可变借用检查。
  • 因为 RefCell<T> 允许在运行时执行可变借用检查,所以我们可以在即便 RefCell<T> 自身是不可变的情况下修改其内部的值。(可以包一个可变的值,即使RefCell<T>是不可变的,就是所谓的内部可变性模式
fn main() {
    // 借用规则的一个推论是当有一个不可变值时,不能可变地借用它。
    let x = 5;
    let y = &mut x;


    let mut c = 10;
    let d = &c;//因为是&c是不可变的引用,所以不能使用解引用
    *d = 20;
}

问题代码

#![allow(unused)]
fn main() {}

pub trait Messenger {