大綱#
- 我假裝這裡有東西,但實際上沒有。
- 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
,但實際上並不是。與一些謊言不同,這實際上有益於代碼。
在實踐中,我看到它主要用於兩種情況:
- 持有生命週期說明符,限制其包含的結構體的生命週期。這對於將生命週期人為地附加到原始指針上可能很有用。
struct PointerWithLifetime<'a, T> {
pointer: *const T,
_marker: std::marker::PhantomData<&'a ()>,
}
- 為了模擬持有類型 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
枚舉類型,其中包含兩個變體 Some
和 None
。這是表示可能存在或不存在的值的推薦方式,而不是使用空指針。它就像一個小的安全包裝器,您應該使用它,除非您知道自己在做什麼並準備好了後果,或者是單獨工作。
但是,使用空指針和使用 None
之間存在顯著差異。首先,Option<T>
是一個擁有的類型,而原始指針只是指向內存中的一些空間。這意味著,除了在使用原始指針時必須小心的不安全操作和所有其他事項之外,None
的大小可以變化,適應它所包圍的東西的大小。它只是一個枚舉類型 Option<T>
的變體,如果 T
是 Sized
,則任何 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」類型。