rustのArcの内部構造

rustのスマートポインタの構成については,Raph Levien氏の以下の図が参考になります.

今回はここで特にArcについて見ていきたいと思います.

Arcは以下のように定義されています(一部抜粋).

pub struct Arc<T: ?Sized> {
    ptr: NonNull<ArcInner<T>>,
    phantom: PhantomData<T>,
}

struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUsize,
    weak: atomic::AtomicUsize,
    data: T,
}

impl<T: ?Sized> Deref for Arc<T> {
    type Target = T;

    #[inline]
    fn deref(&self) -> &T {
        &self.inner().data
    }
}

具体例

具体例としてArc<String>で確認してみます.

Raph Levien氏の図を参考に書くと,Arc<String>の構造は以下のようになります.ちなみにString自体はVec<u8>です

f:id:mm_i:20190212220649p:plain
Arc<String>

実際にメモリ上の表現を確認してみます(playgroundへのリンク).

as_raw_bytes()で与えられたリファレンスを[u8]のスライスとして解釈してprintします.

use std::sync::Arc;

fn as_raw_bytes<'a, T: ?Sized>(x: &'a T) -> &'a [u8] {
    unsafe { std::slice::from_raw_parts(x as *const T as *const u8, std::mem::size_of_val(x)) }
}

fn main() {
    let s: Arc<String> = Arc::new(format!("str"));

    println!("[1] {:?}", as_raw_bytes(&s));
    println!("[2] {:?}", as_raw_bytes(&*s));
    println!("[3] {:?}", as_raw_bytes(&**s));
}

実行結果

[1] [96, 90, 157, 11, 128, 85, 0, 0]
[2] [64, 90, 157, 11, 128, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[3] [115, 116, 114]

[1]がArcが保持するArcInnerへのポインタのアドレスです.

[2]は,ArcInnerの中身...ではなくてArcのDerefによって,ArcInnerのdata部分が表示されています. これは今回の場合StringすなわちVec<u8>です.Vec<u8>は最初に要素先頭へのポインタ,その次が要素の長さ(len),最後が容量(cap)です.

[3]はStringが保持する文字列("str")です.

さて,ArcInnderのstrong countおよびweak countを確認するにはどうしたら良いでしょうか. ArcInnerへのリファレンスが作れれば良いのですが,ArcInner自体はpublicでないのでそれはできません.

そこで必殺技としてtransmute_copyを使って無理やりArcのポインタをu8の配列に変換させて表示させてみます.

{
    let s = s.clone();
    let arc_inner = unsafe {
        std::mem::transmute_copy::<
            Arc<String>,
            &[u8; std::mem::size_of::<atomic::AtomicUsize>() * 2
                 + std::mem::size_of::<String>()],
        >(&s)
    };
    println!("{:?}", &arc_inner[..]);
    println!("strong={}, weak={}", Arc::strong_count(&s), Arc::weak_count(&s));
}
[2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 90, 157, 11, 128, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
strong=2, weak=0

これでstrong countおよびweak countが確認できました. (ArcInnerのweak countは1ですが,Arc::weak_count()はweak countから1引いた数を返すのでこれで合っています)

ちなみにtransmute_copyではなくtransumuteを使うと,Arcのデストラクタが呼ばれなくなるのでメモリリークします.

into_rawfrom_raw

Arcにはinto_raw()from_raw()というメソッドがあります.

Arc::into_raw()はArcが保持するTを指すraw pointerを返します. また,Arc::from_raw()はraw pointerからArcを作成します.

ここで,Arc::from_raw()で渡すraw pointerは,Arc::into_raw()で得られたものでなければならないとドキュメントに書いてあります. というのもArc::from_raw()は与えられたraw pointerからstrong countとweak countのサイズ分だけのオフセットを引いたアドレスを使ってArcを作成するからです.

https://github.com/rust-lang/rust/blob/1.32.0/src/liballoc/sync.rs#L407

    pub unsafe fn from_raw(ptr: *const T) -> Self {
        // Align the unsized value to the end of the ArcInner.
        // Because it is ?Sized, it will always be the last field in memory.
        let align = align_of_val(&*ptr);
        let layout = Layout::new::<ArcInner<()>>();
        let offset = (layout.size() + layout.padding_needed_for(align)) as isize;

        // Reverse the offset to find the original ArcInner.
        let fake_ptr = ptr as *mut ArcInner<T>;
        let arc_ptr = set_data_ptr(fake_ptr, (ptr as *mut u8).offset(-offset));

        Arc {
            ptr: NonNull::new_unchecked(arc_ptr),
            phantom: PhantomData,
        }
    }

逆に言えばこういうデータ構造を最初にメモリに作成すればArc::from_raw()からArcが作成できるわけですね. おそらくバージョン・環境依存の動作だと思いますし,需要は謎ですが...


一年以上振りにrustを触っていますが,スマートポインタの内部構造で少し混乱したので書いてみました.この辺り,コンパイラがderef coercionやauto dereferenceなど勝手にやってくれる分,少々分かりにくい気がします. といってもrustでは基本的にポインタ演算はできませんし,コンパイラに型チェックまかしておけば困ることはほぼないのですが. よく設計されているなとつくずく思います.