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 呢?

這個問題很重要,因爲如果不回答這個問題,固定對象就沒有任何價值。下面列出了使用固定對象的原因:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_JDDbio0KMf7tELpN0uqBA