banner
davirain

davirain

twitter
github
知乎
twitter

在 Rust 中沒有任何東西

大綱#

  • 我假裝這裡有東西,但實際上沒有。
    • PhantomData
  • 這裡現在是空的
    • Null
    • Option::None
  • 這裡永遠不會有東西。
    • 空元組
  • 我會讓你一直等到時間的盡頭,空手而歸。
    • 永遠類型

這是一個關於 Rust 語言中表達「nothing」的一些方式的短篇選集。

在編程中,「nothing」這個概念有幾種不同的解釋:

  • 「我假裝這裡有東西,但實際上沒有。」
  • 「這裡現在是空的。」
  • 「這裡永遠不會有東西。」
  • 「我會讓你一直等到時間的盡頭,空手而歸。」

雖然這聽起來像是我的前任說的最後一句話,但我很好。

我假裝這裡有東西,但實際上沒有。(“I’m pretending like there’s something here, but there actually isn’t.”)#

PhantomData#

Rust 標準庫中有很多高品質的代碼,但是很少有像std::marker::PhantomData這樣純淨優雅的示例。(它的實現和std::mem::drop一樣優雅純粹。)

PhantomData<T>是一種零大小的類型,無論 T 是什麼。這就像對編譯器撒一個小謊言:你聲稱持有一個T,但實際上並不是。與一些謊言不同,這實際上有益於代碼。

在實踐中,我看到它主要用於兩種情況:

  1. 持有生命週期說明符,限制其包含的結構體的生命週期。這對於將生命週期人為地附加到原始指針上可能很有用。
struct PointerWithLifetime<'a, T> {
    pointer: *const T,
    _marker: std::marker::PhantomData<&'a ()>,
}
  1. 為了模擬持有類型 T 的值,當實際值由另一個系統持有(或管理)時。您可能會在與非傳統存儲模型或 FFI 交互時看到這種情況。
mod external {
    pub fn get(location: u64) -> Vec<u8> { /* ... */ }
}

struct Slot<T> {
    location: u64,
    _marker: std::marker::PhantomData<T>,
}

impl<T: From<Vec<u8>>> Slot<T> {
    fn get(&self) -> T {
        T::from(external::get(self.location))
    }
}

這裡現在是空的。(“There is nothing here now.”)#

Null#

Rust 中沒有 null。

你被欺騙了,可能還被控制了。我懂的。「哦,Null 沒有任何問題。」

在安全的 Rust 中這是正確的。

然而,有時需要拔掉創可貼,探究表面下正在發生的事情。

let n: *const i32 = std::ptr::null();
unsafe {
    println!("{}", *n); // Segmentation fault
}

(提醒:原始指針只能在不安全的代碼塊中進行解引用。)

Rust 的設計使您很少需要深入研究指針操作。您可能會在與 C 代碼交互時或者在使用 Rust 重寫 Quake III 時遇到原始指針(*const 和 *mut 類型)。

Option::None#

標準庫提供了 Option 枚舉類型,其中包含兩個變體 SomeNone。這是表示可能存在或不存在的值的推薦方式,而不是使用空指針。它就像一個小的安全包裝器,您應該使用它,除非您知道自己在做什麼並準備好了後果,或者是單獨工作。

但是,使用空指針和使用 None 之間存在顯著差異。首先,Option<T> 是一個擁有的類型,而原始指針只是指向內存中的一些空間。這意味著,除了在使用原始指針時必須小心的不安全操作和所有其他事項之外,None 的大小可以變化,適應它所包圍的東西的大小。它只是一個枚舉類型 Option<T> 的變體,如果 TSized,則任何 Option<T> 值都至少與 T 一樣大,包括 None。而 *const T (當 T: Sized 時) 總是與 usize 相同大小。

類型 | 大小
*const T | 8 (平台相關)
Option<&T> | 8 (平台相關)
Option<std::num::NonZeroU8> | 1
Option<u8> | 2
Option<std::num::NonZeroU32> | 4
Option<u32> | 8
Option<std::num::NonZeroU128> | 16
Option<u128> | 24

這裡永遠不會有東西(“There will never be anything here.”)#

空元組#

空元組寫作空括號 ()

我曾經寫過 Java 代碼。雖然不是完美的,但至少很有品位。在 Java 中,具有 void 返回類型的方法不返回值,無論您給出什麼或多少內容。

空元組在 Rust 中具有類似的作用:不返回實際值的函數隱式返回空元組。但是,它的用途比這更加多樣化。

由於空元組是一個值(儘管是沒有內容且大小為零的值),也是一種類型,因此有時將其用於參數化 Result 類型以表示一個不提供有意義反饋的易錯函數可能會很有用。

impl Partner {
    fn process_request(&mut self, proposition: Proposition) -> Result<(), (u32, RejectionReason)> {
        use std::time::{SystemTime, Duration};
        use chrono::prelude::*;

        self.last_request = SystemTime::now();

        if SystemTime::now().duration_since(self.last_request).unwrap() < Duration::from_secs(60 * 60 * 24 * 7) {
            Err((429, RejectionReason::TooManyRequests))
        } else if proposition.deposit < self.minimum_required_deposit {
            Err((402, RejectionReason::PaymentRequired))
        } else if SystemTime::now().duration_since(self.created_at).unwrap() < Duration::from_secs(60 * 60 * 24 * 366 * 18) {
            Err((451, RejectionReason::UnavailableForLegalReasons))
        } else if Local::now().hours() < 19 {
            Err((425, RejectionReason::TooEarly))
        } else if Local::now().hours() > 20 {
            Err((503, RejectionReason::ServiceUnavailable))
        } else if proposition.len() >= 6 {
            Err((413, RejectionReason::ContentTooLarge))
        } else if !proposition.flushed() {
            Err((409, RejectionReason::Conflict))
        } else if !matches!(proposition.origin_address, Location::Permanent(..)) {
            Err((417, RejectionReason::ExpectationFailed))
        } else {
            Ok(())
        }
    }
}

我會讓你一直等到時間的盡頭,空手而歸。(“I’m going to leave you, waiting here, empty-handed, until the end of time.”)#

永遠類型#

如何調用一個函數的返回類型,它不僅不返回值,而且根本永遠不會返回?嗯,您可以嘗試所有傳統方法都無濟於事 - 您將永遠無法在那一點之後繼續,因此需要一些精細的處理。

這就是所謂的「never」類型。以下是幾種遇到它的方式

let never_loop = loop {}; // loop never exits
let never_panic = panic!(); // panic terminates execution

let value: u32 = match Some(1) {
    Some(x) => x,
    None => return, // `return` is of type never
};

雖然語法仍處於實驗階段,但「never」類型用感嘆號!表示。與此同時,您可以使用 Infallible 作為替代。

當實現一個具有您永遠不需要的關聯類型的 trait 時,「never」類型可能會很有用。再次以 Result 為例:

trait FailureLogProvider {
    type Error;
    fn get_failure_logs(&self) -> Result<Vec<FailureLog>, Self::Error>;
}

impl FailureLogProvider for Partner {
    type Error = !;
    fn get_failure_logs(&self) -> Result<Vec<FailureLog>, Self::Error> {
        Ok(self.failure_log)
    }
}

在示例中的函數實現始終成功,但是 trait 允許實現失敗。為了表示這一點,關聯的 Error 類型是「never」類型。

原文

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。