Rust编程语言入门之智能指针

这篇具有很好参考价值的文章主要介绍了Rust编程语言入门之智能指针。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

智能指针

智能指针(序)

相关的概念

  • 指针:一个变量在内存中包含的是一个地址(指向其它数据)
  • Rust 中最常见的指针就是”引用“
  • 引用:
    • 使用 &
    • 借用它指向的值
    • 没有其余开销
    • 最常见的指针类型

智能指针

  • 智能指针是这样一些数据结构:
    • 行为和指针相似
    • 有额外的元数据和功能

引用计数(Reference counting)智能指针类型

  • 通过记录所有者的数量,使一份数据被多个所有者同时持有
  • 并在没有任何所有者时自动清理数据

引用和智能指针的其它不同

  • 引用:只借用数据
  • 智能指针:很多时候都拥有它所指向的数据

智能指针的例子

  • String 和 Vec<T>

  • 都拥有一片内存区域,且允许用户对其操作

  • 还拥有元数据(例如容量等)

  • 提供额外的功能或保障(String 保障其数据是合法的 UTF-8 编码)

智能指针的实现

  • 智能指针通常使用 Struct 实现,并且实现了:
    • Deref 和 Drop 这两个 trait
  • Deref trait:允许智能指针 struct 的实例像引用一样使用
  • Drop trait:允许你自定义当智能指针实例走出作用域时的代码

本章内容

  • 介绍标准库中常见的智能指针
    • Box<T>:在 heap 内存上分配值
    • Rc<T>:启用多重所有权的引用计数类型
    • Ref<T>RefMut<T>,通过 RefCell<T>访问:在运行时而不是编译时强制借用规则的类型
  • 此外:
    • 内部可变模型(interior mutability pattern):不可变类型暴露出可修改其内部值的 API
    • 引用循环(reference cycles):它们如何泄露内存,以及如何防止其发生。

一、使用Box<T> 来指向 Heap 上的数据

Box<T>

  • Box<T> 是最简单的智能指针:
    • 允许你在 heap 上存储数据(而不是 stack)
    • stack 上是指向 heap 数据的指针
    • 没有性能开销
    • 没有其它额外功能
    • 实现了 Deref trait 和 Drop trait

Box<T> 的常用场景

  • 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道它的确切大小。
  • 当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制。
  • 使用某个值时,你只关心它是否实现了特定的 trait,而不关心它的具体类型。

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

fn main() {
  let b = Box::new(5);
  println!("b = {}", b);
} // b 释放存在 stack 上的指针 heap上的数据

使用 Box 赋能递归类型

  • 在编译时,Rust需要知道一个类型所占的空间大小
  • 而递归类型的大小无法再编译时确定
  • 但 Box 类型的大小确定
  • 在递归类型中使用 Box 就可解决上述问题
  • 函数式语言中的 Cons List

关于 Cons List

  • Cons List 是来自 Lisp 语言的一种数据结构
  • Cons List 里每个成员由两个元素组成
    • 当前项的值
    • 下一个元素
  • Cons List 里最后一个成员只包含一个 Nil 值,没有下一个元素 (Nil 终止标记)

Cons List 并不是 Rust 的常用集合

  • 通常情况下,Vec 是更好的选择
  • (例子)创建一个 Cons List
use crate::List::{Cons, Nil};

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

enum List {  // 报错
  Cons(i32, List),
  Nil,
}
  • (例)Rust 如何确定为枚举分配的空间大小
enum Message {
  Quit,
  Move {x: i32, y: i32},
  Write(String),
  ChangeColor(i32, i32, i32),
}

使用 Box 来获得确定大小的递归类型

  • Box 是一个指针,Rust知道它需要多少空间,因为:
    • 指针的大小不会基于它指向的数据的大小变化而变化
use crate::List::{Cons, Nil};

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

enum List {  
  Cons(i32, Box<List>),
  Nil,
}
  • Box
    • 只提供了”间接“存储和 heap 内存分配的功能
    • 没有其它额外功能
    • 没有性能开销
    • 适用于需要”间接“存储的场景,例如 Cons List
    • 实现了 Deref trait 和 Drop trait

二、Deref Trait(1)

Deref Trait

  • 实现 Deref Trait 使我们可以自定义解引用运算符 * 的行为。
  • 通过实现 Deref,智能指针可像常规引用一样来处理

解引用运算符

  • 常规引用是一种指针
fn main() {
  let x = 5;
  let y = &x;
  
  assert_eq!(5, x);
  assert_eq!(5, *y);
}

把 Box<T> 当作引用

  • Box<T> 可以替代上例中的引用
fn main() {
  let x = 5;
  let y = Box::new(x);
  
  assert_eq!(5, x);
  assert_eq!(5, *y);
}

定义自己的智能指针

  • Box<T> 被定义成拥有一个元素的 tuple struct
  • (例子)MyBox<T>
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);
}

实现 Deref Trait

  • 标准库中的 Deref trait 要求我们实现一个 deref 方法:
    • 该方法借用 self
    • 返回一个指向内部数据的引用
  • (例子)
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 x = 5;
  let y = MyBox::new(x);  
  
  assert_eq!(5, x);
  assert_eq!(5, *y);  // *(y.deref())
}

三、Deref Trait (2)

函数和方法的隐式解引用转化(Deref Coercion)

  • 隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性
  • 假设 T 实现了 Deref trait:
    • Deref Coercion 可以把 T 的引用转化为 T 经过 Deref 操作后生成的引用
  • 当把某类型的引用传递给函数或方法时,但它的类型与定义的参数类型不匹配:
    • Deref Coercion 就会自动发生
    • 编译器会对 deref 进行一系列调用,来把它转为所需的参数类型
      • 在编译时完成,没有额外性能开销
use std::ops::Deref;

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

fn main() {
  let m = MyBox::new(String::from("Rust"));
  
  // &m &MyBox<String> 实现了 deref trait
  // deref &String
  // deref &str
  hello(&m);
  hello(&(*m)[..]);
  
  hello("Rust");
}

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 x = 5;
  let y = MyBox::new(x);  
  
  assert_eq!(5, x);
  assert_eq!(5, *y);  // *(y.deref())
}

解引用与可变性

  • 可使用 DerefMut trait 重载可变引用的 * 运算符
  • 在类型和 trait 在下列三种情况发生时,Rust会执行 deref coercion:
    • 当 T:Deref<Target=U>,允许 &T 转换为 &U
    • 当 T:DerefMut<Target=U>,允许 &mut T 转换为 &mut U
    • 当 T:Deref<Target=U>,允许 &mut T 转换为 &U

四、Drop Trait

Drop Trait

  • 实现 Drop Trait,可以让我们自定义当值将要离开作用域时发生的动作。
    • 例如:文件、网络资源释放等
    • 任何类型都可以实现 Drop trait
  • Drop trait 只要求你实现 drop 方法
    • 参数:对self 的可变引用
  • Drop trait 在预导入模块里(prelude)
/*
 * @Author: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @Date: 2023-04-13 21:39:51
 * @LastEditors: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @LastEditTime: 2023-04-13 21:46:50
 * @FilePath: /smart/src/main.rs
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 
 */
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.")
}

运行

smart on  master [?] is 📦 0.1.0 via 🦀 1.67.1 
➜ cargo run         
   Compiling smart v0.1.0 (/Users/qiaopengjun/rust/smart)
warning: unused variable: `c`
  --> src/main.rs:20:9
   |
20 |     let c = CustomSmartPointer {data: String::from("my stuff")};
   |         ^ 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:21:9
   |
21 |     let d = CustomSmartPointer {data: String::from("other stuff")};
   |         ^ help: if this is intentional, prefix it with an underscore: `_d`

warning: `smart` (bin "smart") generated 2 warnings (run `cargo fix --bin "smart"` to apply 2 suggestions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/smart`
CustomSmartPointers created.
Dropping CustomSmartPointer with data: `other stuff`!
Dropping CustomSmartPointer with data: `my stuff`!

smart on  master [?] is 📦 0.1.0 via 🦀 1.67.1 took 3.6s 

使用 std::mem::drop 来提前 drop 值

  • 很难直接禁用自动的 drop 功能,也没必要
    • Drop trait 的目的就是进行自动的释放处理逻辑
  • Rust 不允许手动调用 Drop trait 的 drop 方法
    • 但可以调用标准库的 std::mem::drop 函数,来提前 drop 值
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")};
  	drop(c);
    let d = CustomSmartPointer {data: String::from("other stuff")};
    println!("CustomSmartPointers created.")
}

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

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

  • 有时,一个值会有多个所有者
  • 为了支持多重所有权:Rc<T>
    • reference couting(引用计数)
    • 追踪所有到值的引用
    • 0 个引用:该值可以被清理掉

Rc<T>使用场景

  • 需要在 heap上分配数据,这些数据被程序的多个部分读取(只读),但在编译时无法确定哪个部分最后使用完这些数据
  • Rc<T> 只能用于单线程场景

例子

  • Rc<T> 不在预导入模块(prelude)
  • Rc::clone(&a) 函数:增加引用计数
  • Rc::strong_count(&a):获得引用计数
    • 还有 Rc::weak_count 函数
  • (例子)
    • 两个 List 共享 另一个 List 的所有权
/*
 * @Author: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @LastEditTime: 2023-04-13 22:37:17
 * @FilePath: /smart/src/lib.rs
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
enum List {
    Cons(i32, Box<List>),
    Nil,
}

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

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let b = Cons(3, Box::new(a));
    let c = Cons(4, Box::new(a));  // 报错
}

优化修改一

/*
 * @Author: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @LastEditTime: 2023-04-13 22:45:15
 * @FilePath: /smart/src/lib.rs
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
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)))));
    // a.clone() // 深度拷贝操作

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

优化修改二

/*
 * @Author: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 qiaopengjun0@gmail.com
 * @LastEditTime: 2023-04-13 22:51:04
 * @FilePath: /smart/src/lib.rs
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
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));

    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 of scope = {}", Rc::strong_count(&a));
}

Rc::clone() vs 类型的 clone() 方法

  • Rc::clone():增加引用,不会执行数据的深度拷贝操作
  • 类型的 clone():很多会执行数据的深度拷贝操作

Rc<T>

  • Rc<T> 通过不可变引用,使你可以在程序不同部分之间共享只读数据

  • 但是,如何允许数据变化呢?

六、RefCell<T> 和内部可变性

内部可变性(interior mutability)

  • 内部可变性是Rust的设计模式之一
  • 它允许你在只持有不可变引用的前提下对数据进行修改
    • 数据结构中使用了 unsafe 代码来绕过 Rust 正常的可变性和借用规则

RefCell<T>

  • Rc<T> 不同, RefCell<T> 类型代表了其持有数据的唯一所有权。

回忆一下:借用规则

  • 在任何给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用
  • 引用总是有效的

RefCell<T> 与 Box<T> 的区别

Box<T>

  • 编译阶段强制代码遵守借用规则
  • 否则出现错误

RefCell<T>

  • 只会在运行时检查借用规则
  • 否则触发 panic

借用规则在不同阶段进行检查的比较

编译阶段

  • 尽早暴露问题
  • 没有任何运行时开销
  • 对大多数场景是最佳选择
  • 是Rust的默认行为

运行时

  • 问题暴露延后,甚至到生产环境
  • 因借用计数产生些许性能损失
  • 实现某些特定的内存安全场景(不可变环境中修改自身数据)

RefCell<T>

  • Rc<T>相似,只能用于单线程场景

选择Box<T>、Rc<T>、RefCell<T>的依据

说明 Box<T> Rc<T> RefCell<T>
同一数据的所有者 一个 多个 一个
可变性、借用检查 可变、不可变借用(编译时检查) 不可变借用(编译时检查) 可变、不可变借用(运行时检查)
  • 其中:即便 RefCell<T>本身不可变,但仍能修改其中存储的值

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

fn main() {
  let x = 5;
  let y = &mut x; // 报错 cannot borrow as mutable
}

例子:

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

pub struct LimitTracker<'a, T: 'a + Message> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, value: 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're used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You're 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(&mut self, message: &str) { // 报错
            self.sent_messages.push(String::from(message));
        }
    }

    #[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);
    }
}

修改之后:

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

pub struct LimitTracker<'a, T: 'a + Message> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &T, value: 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're used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger
                .send("Warning: You're 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() {
        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);
    }
}

使用RefCell<T>在运行时记录借用信息

  • 两个方法(安全接口):
    • borrow 方法
      • 返回智能指针 Ref<T>,它实现了 Deref
    • borrow_mut 方法
      • 返回智能指针 RefMut<T>,它实现了 Deref
  • RefCell<T> 会记录当前存在多少个活跃的 Ref<T>RefMut<T> 智能指针:
    • 每次调用 borrow:不可变借用计数加1
    • 任何一个 Ref<T>的值离开作用域被释放时:不可变借用计数减1
    • 每次调用 borrow_mut:可变借用计数加1
    • 任何一下 RefMut<T> 的值离开作用域被释放时:可变借用计数减1
  • 以此技术来维护借用检查规则:
    • 任何一个给定时间里,只允许拥有多个不可变借用或一个可变借用。

将 Rc<T> 和 RefCell<T> 结合使用来实现一个拥有多重所有权的可变数据

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

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

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);
}

运行

refdemo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 
➜ cargo run        
   Compiling refdemo v0.1.0 (/Users/qiaopengjun/rust/refdemo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/refdemo`
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))

refdemo on  master [?] is 📦 0.1.0 via 🦀 1.67.1 
➜ 

其它可实现内部可变性的类型

  • Cell<T>:通过复制来访问数据
  • Mutex<T>:用于实现跨线程情形下的内部可变性模式

七、循环引用可导致内存泄漏

Rust可能发生内存泄漏

  • Rust的内存安全机制可以保证很难发生内存泄漏,但不是不可能。
  • 例如使用 Rc<T>RefCell<T>就可能创造出循环引用,从而发生内存泄漏:
    • 每个项的引用数量不会变成0,值也不会被处理掉。
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());
}

防止内存泄漏的解决办法

  • 依靠开发者来保证,不能依靠Rust
  • 重新组织数据结构:一些引用来表达所有权,一些引用不表达所有权
    • 循环引用中的一部分具有所有权关系,另一部分不涉及所有权关系
    • 而只有所有权关系才影响值的清理

防止循环引用 把Rc<T>换成Weak<T>

  • Rc::cloneRc<T>实例的 strong_count 加1,Rc<T>的实例只有在 strong_count 为0的时候才会被清理
  • Rc<T>实例通过调用Rc::downgrade方法可以创建值的 Weak Reference (弱引用)
    • 返回类型是 Weak<T>(智能指针)
    • 调用 Rc::downgrade会为 weak_count 加 1
  • Rc<T>使用 weak_count 来追踪存在多少Weak<T>
  • weak_count 不为0并不影响Rc<T>实例的清理

Strong vs Weak

  • Strong Reference(强引用)是关于如何分享 Rc<T>实例的所有权
  • Weak Reference(弱引用)并不表达上述意思
  • 使用 Weak Reference 并不会创建循环引用:
    • 当 Strong Reference 数量为0的时候,Weak Reference 会自动断开
  • 在使用 Weak<T>前,需保证它指向的值仍然存在:
    • Weak<T>实例上调用 upgrade 方法,返回Option<Rc<T>>
use std::rc::Rc;
use std::cell::RefCell;

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

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

修改后:

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

#[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());
  
  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!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

修改后:文章来源地址https://www.toymoban.com/news/detail-415618.html

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

#[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!(
      "leaf 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),
  );
}

到了这里,关于Rust编程语言入门之智能指针的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust编程语言入门之函数式语言特性:-迭代器和闭包

    闭包(closures) 迭代器(iterators) 优化改善 12 章的实例项目 讨论闭包和迭代器的运行时性能 闭包:可以捕获其所在环境的匿名函数。 闭包: 是匿名函数 保存为变量、作为参数 可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算 可从其定义的作用域捕获值

    2023年04月08日
    浏览(44)
  • Rust编程语言入门之cargo、crates.io

    通过 release profile 来自定义构建 在https://crates.io/上发布库 通过 workspaces 组织大工程 从 https://crates.io/来安装库 使用自定义命令扩展 cargo release profile: 是预定义的 可自定义:可使用不同的配置,对代码编译拥有更多的控制 每个 profile 的配置都独立于其它的 profile cargo 主要的

    2023年04月09日
    浏览(52)
  • Rust编程语言入门之最后的项目:多线程 Web 服务器

    在 socket 上监听 TCP 连接 解析少量的 HTTP 请求 创建一个合适的 HTTP 响应 使用线程池改进服务器的吞吐量 优雅的停机和清理 注意:并不是最佳实践 创建项目 main.rs 文件 修改一: 修改二: 修改三: 修改四: 修改五: hello.html 文件 404.html 文件 单线程Web服务器 开启线程 lib.r

    2023年04月25日
    浏览(54)
  • 入门人工智能 —— 学习一门编程语言 python 基础代码编写和运算符介绍(1)

    随着人工智能技术的快速发展,越来越多的年轻人开始关注这个领域。作为入门者,学习人工智能编程语言至关重要。这里将介绍人工智能编程语言Python的基础知识,帮助初学者更好地理解人工智能领域的基本概念和技术。 下面是一些入门 Python 编程语言的基本知识: 安装

    2024年02月09日
    浏览(60)
  • 【编程语言 · C语言 · 函数指针】

    由于指针可以指向任何存储器位置中的地址,因此它们也可以指向可执行代码的开头。 函数指针或函数指针指向内存中函数的可执行代码。函数指针可以存储在数组中,也可以作为参数传递给其他函数。 函数指针声明使用 * 就像使用任何指针一样: (*func_name)  周围的括号很

    2024年02月10日
    浏览(55)
  • 【Rust 基础篇】Rust FFI:连接Rust与其他编程语言的桥梁

    Rust是一种以安全性和高效性著称的系统级编程语言,具有出色的性能和内存安全特性。然而,在现实世界中,我们很少有项目是完全用一种编程语言编写的。通常,我们需要在项目中使用多种编程语言,特别是在与现有代码库或底层系统交互时。为了实现跨语言的互操作性,

    2024年02月15日
    浏览(48)
  • 如何在 macOS 上安装 Rust 编程语言

    安装Rust编程语言在Mac上是一个相对简单的过程,但它可能会涉及多个步骤。在本文中,我将详细说明如何在Mac上安装Rust,并提供一些常见问题的解决方法。请注意,由于软件和工具可能会发生变化,因此建议首先查看Rust官方网站以获取最新的安装说明。 目录 1.打开终端 2

    2024年02月01日
    浏览(54)
  • Go 与 Rust:现代编程语言的深度对比

    在快速发展的软件开发领域中,选择合适的编程语言对项目的成功至关重要。Go 和 Rust 是两种现代编程语言,它们都各自拥有一系列独特的特性和优势。本文旨在深入比较 Go 和 Rust,从不同的角度分析这两种语言,包括性能、语言特性、生态系统、适用场景以及社区支持。

    2024年04月13日
    浏览(45)
  • 【跟小嘉学 Rust 编程】十七、面向对象语言特性

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(84)
  • 【编程】Rust语言入门第4篇 字符串

    Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但字符串不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)。 常见的字符串有两种: str,通常是引用类型, str ,即字符串字面常量,字符串切片。 std::string::String 类型 str 的变

    2024年02月20日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包