「ファットポインタ」という用語はすでにいくつかの文脈で読んだことがありますが、それが正確に何を意味し、Rustでいつ使用されるかはわかりません。ポインタは通常のポインタの2倍の大きさのようですが、理由がわかりません。また、特性オブジェクトと関係があるようです。
「ファットポインタ」という用語はすでにいくつかの文脈で読んだことがありますが、それが正確に何を意味し、Rustでいつ使用されるかはわかりません。ポインタは通常のポインタの2倍の大きさのようですが、理由がわかりません。また、特性オブジェクトと関係があるようです。
回答:
「ファットポインター」という用語は、動的サイズのタイプ(DST)(スライスまたは特性オブジェクト)への参照および生のポインターを指すために使用されます。ファットポインタには、ポインタと、DSTを「完全」にするための情報(長さなど)が含まれています。
Rustで最も一般的に使用されるタイプはDSTではありませんが、コンパイル時に既知の固定サイズです。これらのタイプはSized
トレイトを実装します。動的サイズのヒープバッファを管理するタイプ(のようなVec<T>
)でさえSized
、コンパイラはVec<T>
インスタンスがスタック上で占める正確なバイト数を知っているためです。現在、Rustには4種類のDSTがあります。
[T]
およびstr
)タイプ[T]
(任意のT
)は動的にサイズ変更されます(特別な「文字列スライス」タイプも同様str
です)。そのため、通常は&[T]
または&mut [T]
、つまり参照の背後にのみ表示されます。この参照は、いわゆる「ファットポインタ」です。確認しよう:
dbg!(size_of::<&u32>());
dbg!(size_of::<&[u32; 2]>());
dbg!(size_of::<&[u32]>());
これは(いくつかのクリーンアップを含めて)印刷します:
size_of::<&u32>() = 8
size_of::<&[u32; 2]>() = 8
size_of::<&[u32]>() = 16
したがって、のような通常の型u32
への参照は、配列への参照と同様に8バイトの大きさであることがわかります[u32; 2]
。これらの2つのタイプはDSTではありません。ただし[u32]
、DSTと同様に、DSTへの参照は2倍の大きさです。スライスの場合、DSTを「完了する」追加データは単に長さです。したがって、の表現&[u32]
は次のようになります。
struct SliceRef {
ptr: *const u32,
len: usize,
}
dyn Trait
)トレイトをトレイトオブジェクトとして使用する場合(つまり、タイプが消去され、動的にディスパッチされる場合)、これらのトレイトオブジェクトはDSTです。例:
trait Animal {
fn speak(&self);
}
struct Cat;
impl Animal for Cat {
fn speak(&self) {
println!("meow");
}
}
dbg!(size_of::<&Cat>());
dbg!(size_of::<&dyn Animal>());
これは(いくつかのクリーンアップを含めて)印刷します:
size_of::<&Cat>() = 8
size_of::<&dyn Animal>() = 16
繰り返しになり&Cat
ますCat
が、は通常のタイプであるため、サイズはわずか8バイトです。ただし、これdyn Animal
は特性オブジェクトであるため、動的にサイズ変更されます。そのため、&dyn Animal
16バイトの大きさです。
特性オブジェクトの場合、DSTを完了する追加データは、vtable(vptr)へのポインターです。ここではvtablesとvptrsの概念を完全に説明することはできませんが、これらはこの仮想ディスパッチコンテキストで正しいメソッド実装を呼び出すために使用されます。vtableは静的なデータであり、基本的に各メソッドの関数ポインターのみが含まれています。これにより、特性オブジェクトへの参照は基本的に次のように表されます。
struct TraitObjectRef {
data_ptr: *const (),
vptr: *const (),
}
(これは、抽象クラスのvptrがオブジェクト内に格納されるC ++とは異なります。どちらのアプローチにも長所と短所があります。)
最後のフィールドがDSTである構造体を持つことにより、実際に独自のDSTを作成することが可能です。ただし、これはかなりまれです。1つの顕著な例はstd::path::Path
です。
カスタムDSTへの参照またはポインターもファットポインターです。追加のデータは、構造体内のDSTの種類によって異なります。
ではRFC 1861、extern type
機能が導入されました。外部型もDSTですが、それらへのポインターはファットポインターではありません。もっと正確に言えば、RFCが言うように:
Rustでは、DSTへのポインターは、ポイントされているオブジェクトに関するメタデータを伝達します。文字列とスライスの場合、これはバッファの長さです。トレイトオブジェクトの場合、これはオブジェクトのvtableです。externタイプの場合、メタデータは単純
()
です。これは、extern型へのポインタがと同じサイズであることを意味しますusize
(つまり、「ファットポインタ」ではありません)。
ただし、Cインターフェイスを操作していない場合は、おそらくこれらのexternタイプを処理する必要はありません。
上記では、不変の参照のサイズを確認しました。ファットポインターは、可変参照、不変rawポインター、および可変rawポインターに対して同じように機能します。
size_of::<&[u32]>() = 16
size_of::<&mut [u32]>() = 16
size_of::<*const [u32]>() = 16
size_of::<*mut [u32]>() = 16