Rust 筆記:Pin 與 PhantomPinne
在本文中,我們將學習如何在代碼中應用 Pin,並理解分析 Pin 在其他代碼中的用法。
Pin 是什麼?
Pin 是 Rust 中的一個基本特性。它允許開發人員將對象固定在內存中的某個位置,這樣代碼就無法將其移動到任何地方。
這個特性是至關重要的,當一個對象引用其他對象時,這些對象傾向於改變它們在內存中的位置,而不管你如何想要它。在構建鏈表之類的數據結構或使用異步代碼時,這可能會影響代碼並導致未定義的行爲。
如何固定一個對象?
固定一個對象很簡單。Rust 提供了一個 Pin 結構體,允許你固定對象。該結構體是 Rust 標準庫的一部分:std::pin::Pin。
讓我們看看下面的例子:
struct MyStruct {
value: u32
}
fn main() {
let my_struct = MyStruct{ value: 10 };
println!("{}", my_struct.value);
}
這個例子包含一個我們在 main 函數中使用的簡單結構體。在 main 中,我們創建了一個結構體的實例,其值爲 10,然後將該值打印到控制檯。
我們可以用 Box::pin() 來固定 my_struct。正如我們在下面的代碼塊中所做的那樣。
use std::{pin::Pin, marker::PhantomPinned};
struct MyStruct {
value: u32,
_pin: PhantomPinned,
}
fn main() {
let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
value: 10,
_pin: PhantomPinned,
});
println!("{}", my_struct.value);
}
Rust 有兩種類型的內存用於在代碼中存儲值:棧和堆內存。Rust 在棧中存儲編譯時確定大小的數據,在堆中存儲運行時確定大小的數據。棧內存的存儲能力有限,但比堆內存快。但是堆內存比棧內存更大、更靈活。
Box::pin() 在堆中分配內存並將其固定在適當的位置。
更改固定對象上的數據
在固定一個對象後,你會立即陷入的一件事是試圖修改其上的數據。這可能會令人沮喪,但沒有必要害怕。有一種方法可以做到這一點,但它涉及一些打破常規的過程。我們將在 unsafe 塊中執行這個過程。
假設我們想要改變 my_struct 的值,賦值爲 32。如果我們試試 my_struct.value = 32,編譯器將生成一條錯誤消息,告訴你它不能工作。
想要修改 my_struct.value 的值,有幾個步驟,必須遵循:
1,首先,使用 Pin::as_mut(&mut my_struct) 方法返回對固定對象的可變引用。
2,然後,使用該可變引用的 pin::get_unchecked_mut(mut_ref) 方法來引用存儲在 pin 中的對象。
3,最後,使用對象的引用來修改對象值。
現在,所做的任何修改都將反映在固定對象中。
讓我們看看執行這些步驟後代碼是什麼樣子的:
use std::{pin::Pin, marker::PhantomPinned};
struct MyStruct {
value: u32,
_pin: PhantomPinned,
}
fn main() {
let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
value: 10,
_pin: PhantomPinned,
});
println!("{}", my_struct.value);
unsafe {
let mut_ref: Pin<&mut MyStruct> = Pin::as_mut(&mut my_struct);
let mut_pinned: &mut MyStruct = Pin::get_unchecked_mut(mut_ref);
mut_pinned.value = 32;
}
println!("{}", my_struct.value);
}
如果運行該代碼,它將在終端中顯示 my_struct 修改前後的值。
_pin 字段
注意到,我們在對代碼所做的修改中向結構體添加了一個_pin 字段。_pin 是放置在要固定的結構體中的字段。它告訴編譯器該結構體應該被固定。你可以將 Box::pin() 方法應用於沒有_pin 字段的結構體,但它不會將其固定在內存中。
可以用下面的代碼進行測試:
use std::pin::Pin;
struct MyStruct {
value: u32,
}
fn main() {
let mut my_struct: Pin<Box<MyStruct>> = Box::pin(MyStruct {
value: 10,
});
println!("{}", my_struct.value);
my_struct.value = 32;
println!("{}", my_struct.value);
}
如上面的代碼所示,當將_pin 字段應用於結構體並使用 PhantomPinned 對其進行初始化時,第 14 行 (my_struct.value = 32;) 無效。
如果查看_pin 字段,你可能還會問爲什麼需要用 PhantomPinned 初始化?PhantomPinned 是 Rust 用來在結構體上強制綁定固定規則的類型。PhantomPinned 在內存中不保存任何值,除了在結構體上強制綁定固定規則之外,什麼也不做。
固定對象的最大問題之一是安全。你可能已經注意到,在修改固定對象 my_struct 時,我們必須將修改代碼包裝在一個 unsafe 塊中。因此,你必須非常小心。如果不這樣做,可能會在代碼的關鍵部分造成未定義的行爲和其他問題。
那爲什麼要用 Pin 呢?
這個問題很重要,因爲如果不回答這個問題,固定對象就沒有任何價值。下面列出了使用固定對象的原因:
-
在 Rust 中 Pin 一個對象可以確保該對象保持在內存中的固定位置 (這對異步編程至關重要)。
-
固定對象可以幫助防止多個任務訪問相同數據時出現數據競爭和其他併發性問題。
-
在處理異步數據時,通過減少代碼的複製和移動,Pin 還有助於提高性能。
-
PIn 確保某些類型的數據總是在內存中可用。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/_JDDbio0KMf7tELpN0uqBA