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>
です.
実際にメモリ上の表現を確認してみます(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_raw
とfrom_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では基本的にポインタ演算はできませんし,コンパイラに型チェックまかしておけば困ることはほぼないのですが. よく設計されているなとつくずく思います.