Rustの「ファットポインター」とは何ですか?


94

「ファットポインタ」という用語はすでにいくつかの文脈で読んだことがありますが、それが正確に何を意味し、Rustでいつ使用されるかはわかりません。ポインタは通常のポインタの2倍の大きさのようですが、理由がわかりません。また、特性オブジェクトと関係があるようです。


7
ところで、この用語自体はRust固有のものではありません。ファットポインタは通常、ポイントされているオブジェクトのアドレスだけでなく、いくつかの追加データを格納するポインタを指します。ポインタにいくつかのタグビットが含まれていて、それらのタグビットによっては、ポインタがまったくポインタではない場合があります。これは、タグ付きポインタ表現と呼ばれます。(たとえば、多くのSmalltalks VMでは、ポインターはワード整列であり、したがって1で終わることはないため、1ビットで終わるポインターは実際には31/63ビット整数です。)HotSpot JVMはそのファットポインターをOOP(オブジェクト指向)と呼びます。ポインタ)。
イェルクWミッターク

2
ただの提案:私がQ&Aペアを投稿するとき、私は通常、それが自己回答の質問であることと、なぜそれを投稿することにしたのかを説明する小さなメモを書きます。こちらの質問の脚注をご覧ください:stackoverflow.com/q/46147231/5768908
GerardoFurtado19年

@GerardoFurtado私は最初、それを正確に説明するコメントをここに投稿しました。しかし、それは今(私ではなく)削除されました。しかし、はい、私は同意します、しばしばそのようなメモは役に立ちます!
LukasKalbertodt19年

回答:


109

「ファットポインター」という用語は、動的サイズのタイプ(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 Animal16バイトの大きさです。

特性オブジェクトの場合、DSTを完了する追加データは、vtable(vptr)へのポインターです。ここではvtablesとvptrsの概念を完全に説明することはできませんが、これらはこの仮想ディスパッチコンテキストで正しいメソッド実装を呼び出すために使用されます。vtableは静的なデータであり、基本的に各メソッドの関数ポインターのみが含まれています。これにより、特性オブジェクトへの参照は基本的に次のように表されます。

struct TraitObjectRef {
    data_ptr: *const (),
    vptr: *const (),
}

(これは、抽象クラスのvptrがオブジェクト内に格納されるC ++とは異なります。どちらのアプローチにも長所と短所があります。)


カスタムDST

最後のフィールドがDSTである構造体を持つことにより、実際に独自のDSTを作成することが可能です。ただし、これはかなりまれです。1つの顕著な例はstd::path::Pathです。

カスタムDSTへの参照またはポインターもファットポインターです。追加のデータは、構造体内のDSTの種類によって異なります。


例外:外部タイプ

ではRFC 1861extern 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.