主頁 > 後端開發 > Rust編程語言入門之智能指標

Rust編程語言入門之智能指標

2023-04-17 07:22:41 後端開發

智能指標

智能指標(序)

相關的概念

  • 指標:一個變數在記憶體中包含的是一個地址(指向其它資料)
  • 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 [email protected]
 * @Date: 2023-04-13 21:39:51
 * @LastEditors: QiaoPengjun5162 [email protected]
 * @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 [email protected]
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 [email protected]
 * @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 [email protected]
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 [email protected]
 * @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 [email protected]
 * @Date: 2023-04-13 22:32:41
 * @LastEditors: QiaoPengjun5162 [email protected]
 * @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 = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/16/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 = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/16/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 = https://www.cnblogs.com/QiaoPengjun/archive/2023/04/16/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());
}

修改后:

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

本文來自博客園,作者:QIAOPENGJUN,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17324022.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550240.html

標籤:其他

上一篇:java -- File類和遞回

下一篇:三天吃透計算機網路八股文

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more