智能指針 智能指針(序) 相關的概念 指針:一個變數在記憶體中包含的是一個地址(指向其它數據) Rust 中最常見的指針就是”引用“ 引用: 使用 & 借用它指向的值 沒有其餘開銷 最常見的指針類型 智能指針 智能指針是這樣一些數據結構: 行為和指針相似 有額外的元數據和功能 引用計數(Referen ...
智能指針
智能指針(序)
相關的概念
- 指針:一個變數在記憶體中包含的是一個地址(指向其它數據)
- 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