Open1

ほんのちょっと踏み込んでいく Rust

zuribozuribo

PhantomData

  • PhantomData<T> は、Zero Sized Type (ZST) と呼ばれる型で、サイズが 0 で仮想的に与えられた型 T のフィールドが存在しているかのように、コンパイラに伝えることができるものである。
struct PdStruct<T> {
  data: i32,
  pd: PhantomData<T>,
}

上記の例では、pd フィールドの型として PhantomData<T> が指定されており、PdStruct<T> のサイズを増やさないが、コンパイラにあたかも T 型を所有しているかのように振る舞うように伝えることができる。

  • PhantomData<T> は、一般的に生ポインタ、利用されていないライフタイムパラメータ、使用されていない型パラメータとよもに使用される。

生ポインタ

use std::marker::PhantomData;

struct MyRawPtrStruct<T> {
    ptr: *mut T,
    _marker: PhantomData<T>,
}

impl<T> MyRawPtrStruct<T> {
    fn new(t: T) -> MyRawPtrStruct<T> {
        let t = Box::new(t);
        MyRawPtrStruct {
            ptr: Box::into_raw(t),
            _marker: PhantomData,
        }
    }
}
  • MyRawPtrStruct は、ヒープメモリに割り当てられた T を所有するシンプルなスマートポインタである。
  • Rust のコンパイラは、生ポインタのライフタイムや所有権を自動的に推論することはできない。
  • この例における PhantomData<T> は、T が実際にフィールドとして必要ないが、MyRawPtrStruct が実際には生ポインタの先に T があり、実際には T を所有していることを、コンパイラに伝えている。
  • これにより、Rust のコンパイラが、drop する順番や所有権周りの処理を正しく行えるようになる。

使用されていないライフタイムパラメータ

use std::marker::PhantomData;

struct Window<'a, T: 'a> {
    start: *const T,
    end: *const T,
    phantom: PhantomData<&'a T>,
}
  • startend フィールドは生ポインタであり、T 型の値の開始アドレスと終了アドレスをポイントしているが、ライフタイムの情報が含まれておらず、Rust の借用チェッカーが 'a のライフタイムを使うことができない。
  • phantom フィールドによって、ライフタイム 'a の情報を Rust の借用チャッカーに伝えることができ、Window がポイントする先のデータは、Window が使用されている間は drop することができないと判断できるようになる。

使用されていないタイプパラメータ

TBD.

PhantomData<T> の実用例

BorrowedFd

pub struct BorrowedFd<'fd> {
    fd: RawFd,
    _phantom: PhantomData<&'fd OwnedFd>,
}
  • OwnedFd の借用バージョンが BorrowedFd である。
  • これにより BorrowedFd が使われている間は、OwnedFd を drop することはできないことをコンパイラに伝えることができる。

Iter<T>

pub struct Iter<'a, T: 'a> {
    ptr: NonNull<T>,
    end: *const T,
    _marker: PhantomData<&'a T>,
}
  • Iter がライフタイム 'a に紐づいていることを伝えることができる。
  • 参照している T よりも長く Iter は生き残ることはできない。

参考リンク