Rust中的智能指針:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak<T>

来源:https://www.cnblogs.com/liujin-now/archive/2023/04/20/17335275.html
-Advertisement-
Play Games

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


Rust中的智能指針是什麼

智能指針(smart pointers)是一類數據結構,是擁有數據所有權和額外功能的指針。是指針的進一步發展

指針(pointer)是一個包含記憶體地址的變數的通用概念。這個地址引用,或 ” 指向”(points at)一些其 他數據 。引用以 & 符號為標誌並借用了他們所 指向的值。除了引用數據沒有任何其他特殊功能。它們也沒有任何額外開銷,所以在Rust中應用得最多。

智能指針是Rust中一種特殊的數據結構。它與普通指針的本質區別在於普通指針是對值的借用,而智能指針通常擁有對數據的所有權。並且可以實現很多額外的功能。

Rust智能指針有什麼用,解決了什麼問題

它提供了許多強大的抽象來幫助程式員管理記憶體和併發。其中一些抽象包括智能指針和內部可變性類型,它們可以幫助你更安全、更高效地管理記憶體,例如Box<T> 用於在堆上分配值。Rc<T> 是一種引用計數類型,可以實現數據的多重所有權。RefCell<T> 提供內部可變性,可用於實現對同一數據的多個可變引用

它們在標準庫中定義,可以用來更靈活地管理記憶體,智能指針的一個特點就是實現了Drop和Deref這兩個trait。其中Drop trait中提供了drop方法,在析構時會去調用。Deref trait提供了自動解引用的能力,讓我們在使用智能指針的時候不需要再手動解引用了

Rust有哪些常用智能指針

  • Box<T>是最簡單的智能指針,它允許你在堆上分配值併在離開作用域時自動釋放記憶體。
  • Rc<T>Arc<T>是引用計數類型,它們允許多個指針指向同一個值。當最後一個指針離開作用域時,值將被釋放。Rc<T>不是線程安全的,而Arc<T>是線程安全的。

內部可變性類型允許你在不可變引用的情況下修改內部值。Rust中有幾種內部可變性類型,包括Cell<T>RefCell<T>UnsafeCell<T>

  • Cell<T>是一個內部可變性類型,它允許你在不可變引用的情況下修改內部值。Cell<T>只能用於Copy類型,因為它通過複製值來實現內部可變性。
  • RefCell<T>也是一個內部可變性類型,它允許你在不可變引用的情況下修改內部值。與Cell<T>不同,RefCell<T>可以用於非Copy類型。它通過借用檢查來確保運行時的安全性。
  • UnsafeCell<T>是一個底層的內部可變性類型,它允許你在不可變引用的情況下修改內部值。與Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何運行時檢查來確保全全性。因此,使用UnsafeCell<T>可能會導致未定義行為。

此外,Rust還提供了一種弱引用類型Weak<T>,它可以與Rc<T>Arc<T>一起使用來創建迴圈引用。Weak<T>不會增加引用計數,因此它不會阻止值被釋放。

Box<T>

Box<T>是最簡單的智能指針,它允許你在堆上分配值併在離開作用域時自動釋放記憶體。

Box<T>通常用於以下情況:

  • 當你有一個類型,但不確定它的大小時,可以使用Box<T>來在堆上分配記憶體。例如,遞歸類型通常需要使用Box<T>來分配記憶體。
  • 當你有一個大型數據結構並希望在棧上分配記憶體時,可以使用Box<T>來在堆上分配記憶體。這樣可以避免棧溢出的問題。
  • 當你希望擁有一個值並只關心它的類型而不是所占用的記憶體時,可以使用Box<T>。例如,當你需要將一個閉包傳遞給函數時,可以使用Box<T>來存儲閉包。

總之,當你需要在堆上分配記憶體並管理其生命周期時,可以考慮使用Box<T>

下麵是一個簡單的例子:

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}
複製代碼

這裡定義了變數 b,其值是一個指向被分配在堆上的值 5 的 Box。這個程式會列印出 b = 5;在這個例子 中,我們可以像數據是儲存在棧上的那樣訪問 box 中的數據。正如任何擁有數據所有權的值那樣,當像 b 這樣的 box 在 main 的末尾離開作用域時,它將被釋放。這個釋放過程作用於 box 本身(位於棧上) 和它所指向的數據(位於堆上)。

但是Box<T>無法同時在多個地方對同一個值進行引用

enum List { 
Cons(i32, Box), 
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)); 
}
複製代碼

編譯會得出如下錯誤: error[E0382]: use of moved value: a,因為b和c無法同時擁有a的所有權,這個時候我們要用Rc<T>

Rc<T> Reference Counted 引用計數

Rc<T>是一個引用計數類型,它允許多個指針指向同一個值。當最後一個指針離開作用域時,值將被釋放。Rc<T>不是線程安全的,因此不能在多線程環境中使用。

Rc<T>通常用於以下情況:

  • 當你希望在多個地方共用數據時,可以使用Rc<T>。解決了使用Box<T>共用數據時出現編譯錯誤
  • 當你希望創建一個迴圈引用時,可以使用Rc<T>Weak<T>來實現。

下麵是一個簡單的例子,演示如何使用Rc<T>來共用數據:

use std::rc::Rc;

fn main() {
    let data = Rc::new(vec![1, 2, 3]);
    let data1 = data.clone();
    let data2 = data.clone();
    println!("data: {:?}", data);
    println!("data1: {:?}", data1);
    println!("data2: {:?}", data2);
}
複製代碼

這個例子中,我們使用Rc::new來創建一個新的Rc<T>實例。然後,我們使用clone方法來創建兩個新的指針,它們都指向同一個值。由於Rc<T>實現了引用計數,所以當最後一個指針離開作用域時,值將被釋放。

但是Rc<T>在多線程中容易引發線程安全問題,為瞭解決這個問題,又有了Arc<T>

Arc<T> Atomically Reference Counted 原子引用計數

Arc<T>是一個線程安全的引用計數類型,它允許多個指針在多個線程之間共用同一個值。當最後一個指針離開作用域時,值將被釋放。

Arc<T>通常用於以下情況:

  • 當你希望在多個線程之間共用數據時,可以使用Arc<T>,是Rc<T>的多線程版本。
  • 當你希望線上程之間傳遞數據時,可以使用Arc<T>來實現。

下麵是一個簡單的例子,演示如何使用Arc<T>來線上程之間共用數據:

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let data1 = data.clone();
    let data2 = data.clone();

    let handle1 = thread::spawn(move || {
        println!("data1: {:?}", data1);
    });

    let handle2 = thread::spawn(move || {
        println!("data2: {:?}", data2);
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}
複製代碼

這個例子中,我們使用Arc::new來創建一個新的Arc<T>實例。然後,我們使用clone方法來創建兩個新的指針,它們都指向同一個值。接著,我們線上程中使用這些指針來訪問共用數據。由於Arc<T>實現了線程安全的引用計數,所以當最後一個指針離開作用域時,值將被釋放。

Weak<T> 弱引用類型

Weak<T>是一個弱引用類型,它可以與Rc<T>Arc<T>一起使用來創建迴圈引用。Weak<T>不會增加引用計數,因此它不會阻止值被釋放。

當你希望創建一個迴圈引用時,可以使用Rc<T>Arc<T>Weak<T>來實現。

Weak<T>通常用於以下情況:

  • 當你希望觀察一個值而不擁有它時,可以使用Weak<T>來實現。由於Weak<T>不會增加引用計數,所以它不會影響值的生命周期。

下麵是一個簡單的例子,演示如何使用Rc<T>Weak<T>來創建一個迴圈引用:

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

struct Node {
    value: i32,
    next: Option<Rc<Node>>,
    prev: Option<Weak<Node>>,
}

fn main() {
    let first = Rc::new(Node { value: 1, next: None, prev: None });
    let second = Rc::new(Node { value: 2, next: None, prev: Some(Rc::downgrade(&first)) });
    first.next = Some(second.clone());
}
複製代碼

這個例子中,我們定義了一個Node結構體,它包含一個值、一個指向下一個節點的指針和一個指向前一個節點的弱引用。然後,我們創建了兩個節點firstsecond,並使用Rc::downgrade方法來創建一個弱引用。最後,我們將兩個節點連接起來,形成一個迴圈引用。

需要註意的是,由於Weak<T>不會增加引用計數,所以它不會阻止值被釋放。當你需要訪問弱引用指向的值時,可以使用upgrade方法來獲取一個臨時的強引用。如果值已經被釋放,則upgrade方法會返回None

UnsafeCell<T>

UnsafeCell<T>是一個底層的內部可變性類型,它允許你在不可變引用的情況下修改內部值。與Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何運行時檢查來確保全全性。因此,使用UnsafeCell<T>可能會導致未定義行為。

由於UnsafeCell<T>是一個底層類型,它通常不直接用於應用程式代碼。相反,它被用作其他內部可變性類型(如Cell<T>RefCell<T>)的基礎。

下麵是一個簡單的例子,演示如何使用UnsafeCell<T>來修改內部值:

use std::cell::UnsafeCell;

fn main() {
    let x = UnsafeCell::new(1);
    let y = &x;
    let z = &x;
    unsafe {
        *x.get() = 2;
        *y.get() = 3;
        *z.get() = 4;
    }
    println!("x: {}", unsafe { *x.get() });
}
複製代碼

這個例子中,我們使用UnsafeCell::new來創建一個新的UnsafeCell<T>實例。然後,我們創建了兩個不可變引用yz,它們都指向同一個值。接著,在一個unsafe塊中,我們使用get方法來獲取一個裸指針,並使用它來修改內部值。由於UnsafeCell<T>實現了內部可變性,所以我們可以在不可變引用的情況下修改內部值。

需要註意的是,由於UnsafeCell<T>不提供任何運行時檢查來確保全全性,所以使用它可能會導致未定義行為。因此,在大多數情況下,你應該使用其他內部可變性類型(如Cell<T>RefCell<T>),而不是直接使用UnsafeCell<T>

Cell<T>

Cell<T>是一個內部可變性類型,它允許你在不可變引用的情況下修改內部值。Cell<T>只能用於Copy類型,因為它通過複製值來實現內部可變性。

Cell<T>通常用於以下情況:

  • 當你需要在不可變引用的情況下修改內部值時,可以使用Cell<T>來實現內部可變性。
  • 當你需要在結構體中包含一個可變欄位時,可以使用Cell<T>來實現。 下麵是一個簡單的例子,演示如何使用Cell<T>來修改內部值:
use std::cell::Cell;

fn main() {
    let x = Cell::new(1);
    let y = &x;
    let z = &x;
    x.set(2);
    y.set(3);
    z.set(4);
    println!("x: {}", x.get());
}
複製代碼

這個例子中,我們使用Cell::new來創建一個新的Cell<T>實例。然後,我們創建了兩個不可變引用yz,它們都指向同一個值。接著,我們使用set方法來修改內部值。由於Cell<T>實現了內部可變性,所以我們可以在不可變引用的情況下修改內部值。

需要註意的是,由於Cell<T>通過複製值來實現內部可變性,所以它只能用於Copy類型。如果你需要在不可變引用的情況下修改非Copy類型的值,可以考慮使用RefCell<T>

RefCell<T>

RefCell<T>是一個內部可變性類型,它允許你在不可變引用的情況下修改內部值。與Cell<T>不同,RefCell<T>可以用於非Copy類型。

RefCell<T>通過借用檢查來確保運行時的安全性。當你嘗試獲取一個可變引用時,RefCell<T>會檢查是否已經有其他可變引用或不可變引用。如果有,則會發生運行時錯誤。

RefCell<T>通常用於以下情況:

  • 當你需要在不可變引用的情況下修改內部值時,可以使用RefCell<T>來實現內部可變性。

  • 當你需要在結構體中包含一個可變欄位時,可以使用RefCell<T>來實現。

下麵是一個簡單的例子,演示如何使用RefCell<T>來修改內部值:

use std::cell::RefCell;

fn main() {
    let x = RefCell::new(vec![1, 2, 3]);
    let y = &x;
    let z = &x;
    x.borrow_mut().push(4);
    y.borrow_mut().push(5);
    z.borrow_mut().push(6);
    println!("x: {:?}", x.borrow());
}
複製代碼

這個例子中,我們使用RefCell::new來創建一個新的RefCell<T>實例。然後,我們創建了兩個不可變引用yz,它們都指向同一個值。接著,我們使用borrow_mut方法來獲取一個可變引用,並使用它來修改內部值。由於RefCell<T>實現了內部可變性,所以我們可以在不可變引用的情況下修改內部值。

需要註意的是,由於RefCell<T>通過借用檢查來確保運行時的安全性,所以當你嘗試獲取一個可變引用時,如果已經有其他可變引用或不可變引用,則會發生運行時錯誤。from劉金,轉載請註明原文鏈接。感謝!


 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 好家伙,我的包終於開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ...
  • 我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單文件組件模式,即一個組件就是一個文件,但其實 Vue 也是支持使用 JSX 來編寫組件的。這裡不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速瞭解和使用 Vu ...
  • 本章目標:計算屬性是如何實現的?計算屬性緩存原理以及洋蔥模型的應用?在初始化Vue實例時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ...
  • 一、http是什麼 通俗來講,http就是電腦通過網路進行通信的規則,是一個基於請求與響應,無狀態的,應用層協議。常用於TCP/IP協議傳輸數據。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與響應:客戶端請求、服務端響應數據。 無狀態 ...
  • * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關係, * 將對象之間的通信封裝到一個中介者對象中,從而使得各個對象之間的關係更加鬆散。 * 在中介者模式中,對象之間不再直接相互交互,而是通過中介者來中轉消息。 ...
  • 他們集團的信息化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,項目投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多餘。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ...
  • 設計是一個讓人夢想成真過程,開始編碼、測試、調試之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ...
  • 作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基於此我們探索了一種新的技術體系及交付方案來解決如上問題。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...