当前位置: 首页 > article >正文

rust智能指针

文章目录

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

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 {
    fn send(&self, msg: &str);
}

// T: 要求所有实现Messenger特征
pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct MockMessenger {
        sent_messages: Vec<String>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: vec![],
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.push(String::from(message)); //不能修改 MockMessenger 来记录消息,因为 send 方法获取了 self 的不可变引用
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.len(), 1);
    }
}

使用RefCell解决

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

pub trait Messenger {
    fn send(&self, msg: &str);
}

// T: 要求所有实现Messenger特征
pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.borrow_mut().push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        // --snip--
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}

编译

 cargo test
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 18.97s
     Running unittests src/main.rs (target/debug/deps/smartPtr-939870f93d87524c)

running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

多次调用borrow_mut(),只会在运行时报错

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

pub trait Messenger {
    fn send(&self, msg: &str);
}

// T: 要求所有实现Messenger特征
pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage_of_max = self.value as f64 / self.max as f64;

        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage_of_max >= 0.9 {
            self.messenger
                .send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You've used up over 75% of your quota!");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
             let mut one_borrow = self.sent_messages.borrow_mut();
        let mut two_borrow = self.sent_messages.borrow_mut();

        one_borrow.push(String::from(message));
        two_borrow.push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        // --snip--
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
    }
}

编译及运行

 cargo test
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 15.79s
     Running unittests src/main.rs (target/debug/deps/smartPtr-939870f93d87524c)

running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... FAILED

failures:

---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/main.rs:64:49:
already borrowed: BorrowMutError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::it_sends_an_over_75_percent_warning_message

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--bin smartPtr`

结合 Rc<T> 和 RefCell<T> 来拥有多个可变数据所有者

在这里插入图片描述

#[derive(Debug)]
enum List {
    // i33包在RefCell中,说明值可变,RefCell<i32>又包在Rc中,说明这个值可以有多个所有者
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

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

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

编译

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: fields `0` and `1` are never read
 --> src/main.rs:4:10
  |
4 |     Cons(Rc<RefCell<i32>>, Rc<List>),
  |     ---- ^^^^^^^^^^^^^^^^  ^^^^^^^^
  |     |
  |     fields in this variant
  |
  = note: `List` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
  = 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
  |
4 |     Cons((), ()),
  |          ~~  ~~

warning: `smartPtr` (bin "smartPtr") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.96s
     Running `target/debug/smartPtr`
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil))

循环引用可能导致的内存泄露问题

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)))); //下一项指向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); //把Rc::new(Nil)改成了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());
}

Example流程对应的图片如下:

图片1
在这里插入图片描述

图片2
在这里插入图片描述

当main结束,a和b都会被清理掉(对应栈上的内容会被清理掉。堆上的内存不会被清理掉)在这里插入图片描述
在这里插入图片描述

leaf节点是branch节点的child

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    // 叶子节点
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); //leaf.parent.borrow().upgrade()将弱引用智能指针转成Rc智能指针

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    // *leaf.parent.borrow_mut()才能改变里面的值
    // 让leaf.parent的弱引用的方式指向branch,就得到下面输出的Some
    *leaf.parent.borrow_mut() = Rc::downgrade(&branch); //Rc::downgrade: 将Rc智能指针转成弱引用指针

    // 弱引用智能指针并不知道,其包含的值是否被丢弃掉了,必须得转成Rc智能指针
    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    //   Rc::strong_count表示对数据有所有权的引用计数的数量
    //Rc::weak_count表示对数据没有所有权的引用计数的数量
}

编译

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: fields `value` and `children` are never read
 --> src/main.rs:6:5
  |
5 | struct Node {
  |        ---- fields in this struct
6 |     value: i32,
  |     ^^^^^
7 |     parent: RefCell<Weak<Node>>,
8 |     children: RefCell<Vec<Rc<Node>>>,
  |     ^^^^^^^^
  |
  = note: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
  = note: `#[warn(dead_code)]` on by default

warning: `smartPtr` (bin "smartPtr") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.95s
     Running `target/debug/smartPtr`
leaf parent = None
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } })
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}

编译

 cargo run
   Compiling smartPtr v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: fields `value` and `children` are never read
 --> src/main.rs:6:5
  |
5 | struct Node {
  |        ---- fields in this struct
6 |     value: i32,
  |     ^^^^^
7 |     parent: RefCell<Weak<Node>>,
8 |     children: RefCell<Vec<Rc<Node>>>,
  |     ^^^^^^^^
  |
  = note: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
  = note: `#[warn(dead_code)]` on by default

warning: `smartPtr` (bin "smartPtr") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.84s
     Running `target/debug/smartPtr`
leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0

参考

  • 第15章~Box智能指针(Smart Pointers)

http://www.kler.cn/a/391502.html

相关文章:

  • Sigrity SPEED2000 Power Ground Noise Simulation模式如何查看PDS系统的自阻抗操作指导
  • 【Java SE】接口类型
  • Ollama的安装以及大模型下载教程
  • MTSET可溶于DMSO、DMF、THF等有机溶剂,并在水中有轻微的溶解性,91774-25-3
  • 黄色校正电容102j100
  • 使用CubeMX一键配置Freertos
  • ubuntu下openssl签名证书制作流程及验证demo
  • XML Schema 字符串数据类型
  • 干式电抗器的故障诊断和排除方法是什么?
  • 【大数据学习 | HBASE】hbase的写数据流程与hbase插入数据
  • Python教程笔记(1)
  • macOS sw_vers 查看当前系统版本
  • 【题解】—— LeetCode一周小结45
  • 光控资本:“自主可控”将进入新估值阶段
  • 【旷视科技-注册/登录安全分析报告】
  • FlinkSql读取kafka数据流的方法(scala)
  • 实现一个BLE HID鼠标
  • 面向对象的需求分析和设计(一)
  • 使用element UI实现表格行/列合并
  • 【Android】 插件化原理
  • 【网络】HTTP 协议
  • Python驱动ansys执行apdl文件
  • Qwen2.5-Coder-32B-Instruct Docker 部署openai接口
  • 网约车管理:规范发展,保障安全与便捷
  • windows tomcat 报错后如何让窗口不闪退
  • 知识竞赛活动中礼仪小姐有哪些工作,要如何安排