概要#
- 私はここに何かあるように見せかけていますが、実際には何もありません。
- PhantomData
- ここは現在空です
- Null
- Option::None
- ここには決して何もありません。
- 空のタプル
- 私はあなたを時間の終わりまで待たせ、空手で帰らせます。
- 決して型
これは、Rust 言語で「何もない」という概念を表現するいくつかの方法についての短い選集です。
プログラミングでは、「何もない」という概念にはいくつかの異なる解釈があります。
- 「私はここに何かあるように見せかけていますが、実際には何もありません。」
- 「ここは現在空です。」
- 「ここには決して何もありません。」
- 「私はあなたを時間の終わりまで待たせ、空手で帰らせます。」
これは私の前の恋人が言った最後の言葉のように聞こえるかもしれませんが、私は大丈夫です。
私はここに何かあるように見せかけていますが、実際には何もありません。("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
を保持していると主張していますが、実際には保持していません。他のいくつかの嘘とは異なり、これは実際にコードに役立ちます。
実際の使用例では、次の 2 つの場合に主に使用されることがあります。
- ライフタイム指定子を保持し、それを含む構造体のライフタイムを制限する。これは、ライフタイムを原始ポインタに人為的に関連付ける場合に便利です。
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); // セグメンテーション違反
}
(注意:原始ポインタは、unsafe ブロック内でのみデリファレンスできます。)
Rust の設計により、ポインタ操作を深く掘り下げる必要がほとんどありません。原始ポインタ(const およびmut 型)を使用する場合や、Rust で Quake III を書き直す場合に遭遇するかもしれません。
Option::None#
標準ライブラリには、Some
とNone
の 2 つのバリアントを含むOption
列挙型が用意されています。これは、存在するか存在しないかを表すための推奨される方法であり、空のポインタを使用する代わりに使用するべきです。これは小さな安全なラッパーのようなものであり、自分が何をしているかを知っていて結果に対する準備ができているか、または単独で作業している場合を除いて使用するべきです。
ただし、空のポインタとNone
の使用には重要な違いがあります。まず、Option<T>
は所有される型であり、原始ポインタは単にメモリ内の場所を指すものです。これは、原始ポインタを使用する場合には注意が必要な不安全な操作やその他のすべての事項とは異なり、None
のサイズは、囲まれているもののサイズに適応することができます。これは、Option<T>
型のバリアントであるNone
を含むすべてのOption<T>
値が、T
がSized
である場合、少なくともT
と同じサイズであることを意味します。一方、*const T
(T: Sized
の場合)は常にusize
と同じサイズです。
Type | Size
*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.")#
決して型#
値を返さないだけでなく、絶対に戻らない関数の戻り値の型をどのように呼び出すのでしょうか?まあ、従来の方法を試してみることはできますが、それはうまくいきません - そのポイント以降に進むことは絶対にできないため、いくつかの細かい処理が必要です。
これが「決して」型と呼ばれるものです。以下は、それに遭遇するいくつかの方法です。
let never_loop = loop {}; // ループは絶対に終了しない
let never_panic = panic!(); // panicは実行を終了する
let value: u32 = match Some(1) {
Some(x) => x,
None => return, // `return`は決して型の一部です
};
構文はまだ実験的な段階にありますが、「決して」型は感嘆符!で表されます。同時に、Infallible を代わりに使用することもできます。
ある関連型を持つトレイトを実装する場合、「決して」型は非常に便利です。再び、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)
}
}
例の関数実装は常に成功しますが、トレイトは失敗を許容します。これを示すために、関連する Error 型は「決して」型です。