Rust 語言學習之旅(6)

banq發表於2022-09-29

智慧指標
在本章中,我們將揭開智慧指標的神秘面紗。 讓我們探索一下能夠讓我們與最底層記憶體打交道的這些資料結構。 Ferris 說:“讀完這一章之後,即使您覺得仍然不能編寫管理底層記憶體的程式碼也不用覺得不知所措。 本章主要是向您介紹一些有用的工具並簡要了解他們如何工作!”

引用本質上只是表示記憶體中某些位元組起始位置的數字。 它唯一的目的就是表示特定型別的資料存在於何處。 引用與數字的不同之處在於,Rust 將驗證引用自身的生命週期不會超過它指向的內容(否則我們在使用它時會出錯!)。

指標
引用可以轉換成一個更原始的型別,指標(raw pointer)。 像數字一樣,它可以不受限制地複製和傳遞,但是Rust 不保證它指向的記憶體位置的有效性。 有兩種指標型別:

  • *const T - 指向永遠不會改變的 T 型別資料的指標。
  • *mut T - 指向可以更改的 T 型別資料的指標。

指標可以與數字相互轉換(例如usize)。指標可以使用 unsafe 程式碼訪問資料(稍後會詳細介紹)。
記憶體細節:
  • Rust中的引用在用法上與 C 中的指標非常相似,但在如何儲存和傳遞給其他函式上有更多的編譯時間限制。
  • Rust中的指標類似於 C 中的指標,它表示一個可以複製或傳遞的數字,甚至可以轉換為數字型別,可以將其修改為數字以進行指標數學運算。


fn main() {
    let a = 42;
    let memory_location = &a as *const i32 as usize;
    println!("Data is here {}", memory_location);
}

輸出:
Data is here 140732857748572

解引用
訪問或操作 由引用(例如&i32)指向的資料的過程稱為解除引用。有兩種方式透過引用來訪問或運算元據:
  • 在變數賦值期間訪問引用的資料。
  • 訪問引用資料的欄位或方法。

Rust 有一些強大的運算子可以讓我們做到這一點。

運算子 *
* 運算子是一種很明確的解引用的方法。

let a: i32 = 42;
let ref_ref_ref_a: &&&i32 = &&&a;
let ref_a: &i32 = **ref_ref_ref_a;
let b: i32 = *ref_a;



記憶體細節:
  • 因為 i32 是實現了 Copy 特性的原始型別,堆疊上變數 a 的位元組被複制到變數 b 的位元組中。

fn main() {
    let a: i32 = 42;
    let ref_ref_ref_a: &&&i32 = &&&a;
    let ref_a: &i32 = **ref_ref_ref_a;
    let b: i32 = *ref_a;
    println!("{}", b)
}


運算子 .
.運算子用於訪問引用的欄位和方法,它的工作原理更加巧妙。

let f = Foo { value: 42 };
let ref_ref_ref_f = &&&f;
println!("{}", ref_ref_ref_f.value);

哇,為什麼我們不需要在ref_ref_ref_f之前新增***?這是因為 . 運算子會做一些列自動解引用操作。 最後一行由編譯器自動轉換為以下內容。

println!("{}", (***ref_ref_ref_f).value);


除了能夠使用&運算子建立對現有型別資料的引用之外, Rust 給我們提供了能夠建立稱為智慧指標的類引用結構。我們可以在高層次上將引用視為一種型別,它使我們能夠訪問另一種型別. 智慧指標的行為與普通引用不同,因為它們基於程式設計師編寫的內部邏輯進行操作. 作為程式設計師的你就是智慧的一部分。通常,智慧指標實現了 Deref、DerefMut 和 Drop 特徵,以指定當使用 * 和 . 運算子時解引用應該觸發的邏輯。

use std::ops::Deref;
struct TattleTell<T> {
    value: T,
}
impl<T> Deref for TattleTell<T> {
    type Target = T;
    fn deref(&self) -> &T {
        println!("{} was used!", std::any::type_name::<T>());
        &self.value
    }
}
fn main() {
    let foo = TattleTell {
        value: "secret message",
    };
  // 取消引用立即發生在這裡
     // 在 foo 被自動引用之後
     // 函式 `len`
    println!("{}", foo.len());
}


智慧不安全程式碼
智慧指標傾向於經常使用不安全的程式碼。如前所述,它們是與 Rust 中最低階別的記憶體進行互動的常用工具。什麼是不安全程式碼? 不安全程式碼的行為與普通 Rust 完全一樣,除了一些 Rust 編譯器無法保證的功能。不安全程式碼的主要功能是解引用指標。 這意味著將原始指標指向記憶體中的某個位置並宣告“此處存在資料結構!” 並將其轉換為您可以使用的資料表示(例如將*const u8 轉換為u8)。 Rust 無法跟蹤寫入記憶體的每個位元組的含義。 因為 Rust 不能保證在用作 指標 的任意數字上存在什麼,所以它將解引用放在一個 unsafe { ... } 塊中。
智慧指標廣泛地被用來解引用指標,它們的作用得到了很好的證明。

fn main() {
    let a: [u8; 4] = [86, 14, 73, 64];
  // 這是一個原始指標。 獲取記憶體地址
     // 作為數字的東西是完全安全的
    let pointer_a = &a as *const u8 as usize;
    println!("Data memory location: {}", pointer_a);

// 將我們的數字變成指向 f32 的原始指標是
     // 也很安全。
    let pointer_b = pointer_a as *const f32;
    let b = unsafe {
         // 這是不安全的,因為我們告訴編譯器
         // 假設我們的指標是一個有效的 f32 並且
         // 將它的值取消引用到變數 b 中。
         // Rust 沒有辦法驗證這個假設是否正確。
        *pointer_b
    };
    println!("I swear this is a pie! {}", b);
}

相關文章