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日
    浏览(46)
  • Rust编程语言入门之cargo、crates.io

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    2024年02月10日
    浏览(89)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包