rustのArcについてその2

前回

以下で利用したplaygroundのリンク

スライスからのArcの作成

Arcのソースをみていて気づきましたが,impl<T: Clone> From<&[T]> for Arc<[T]>などが実装されており,CopyあるいはCloneが実装されているスライスからArcを作成することができます.(RCも同様です)

例えばこれを使うと,Arc<str> (=Arc<[u8]>)が作れます.

Arc<[T]>はfat pointerとなり,構造的には以下の様になります.

f:id:mm_i:20190214233307p:plain
Arc<T>

x86_64環境なら最初の8byteがデータへのポインタ,そのあと8byteがデータのサイズです.

作成例

let s: Arc<str> = Arc::from("str");
println!("{:?}", as_raw_bytes(&s));
println!("{:?}", as_raw_bytes(&*s));
[64, 10, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[115, 116, 114]

Arc<String>だと文字列にアクセスするまで二回ポインタを辿らないといけませんが(前回の記事参照),これなら一回で済みます.

BoxからのArcの作成

impl<T: ?Sized> From<Box<T>> for Arc<T>も実装されています. 内部動作としてはBoxの中身をArc側にmemcpyして,box側のメモリを解放することになります.

こちらの場合,CopyあるいはCloneが実装されていなくてもArcが作成できます.T: ?SizedなのでArc<[T]>が作れます.

struct A(i32);
let s: Box<[A]> = Box::new([A(0),A(1),A(2)]);
let s: Arc<[A]> = Arc::from(s);
println!("{:?}", as_raw_bytes(&s));
println!("{:?}", as_raw_bytes(&*s));
[224, 11, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0]

まぁこの場合そもそも普通に Arc::new([A(0), A(1), A(2)])すればいいんですが.

Arc<str>Stringから作成したい場合は,String::into_boxed_str()を使ってBox<str>にしてからArcにできます.

let s: Arc<str> = Arc::from(format!("str").into_boxed_str());
println!("{:?}", as_raw_bytes(&s));
println!("{:?}", as_raw_bytes(&*s));
[192, 11, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[115, 116, 114]

VecからのArcの作成

impl<T> From<Vec<T>> for Arc<[T]>もあります.

let s: Arc<[i32]> = Arc::from(vec![0,1,2]);
println!("{:?}", as_raw_bytes(&s));
println!("{:?}", as_raw_bytes(&*s));
[16, 12, 247, 79, 223, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0]

こちらも動作的にはArcが新規に割り当てたメモリ領域にVecのデータを全部移すことになります.

servo_arc

servoにはservo_arcという,Arcの派生実装が含まれています.

以下の様な特徴があります.

  • weakカウントがなくてstrongカウントのみ
  • dynamically-sized type (DST)のサポート

DSTに関しては以前記事を書きましたservo_arcを使うと,以下の様なことができます.

#[derive(Debug)]
struct Header {
    a: u32,
    b: u32,
    c: u32,
};
let header = Header { a: 1, b: 2, c: 3 };
let data: Vec<u32> = vec![4, 5, 6];
let a = servo_arc::Arc::from_header_and_iter(header, data.into_iter());

println!("{:?}", &a);
println!("{:?}", as_raw_bytes(&a));
println!("{:?}", as_raw_bytes(&*a));
HeaderSlice { header: Header { a: 1, b: 2, c: 3 }, slice: [4, 5, 6] }
[240, 44, 64, 90, 168, 127, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0]

Arc::from_header_and_iter()Arc<HeaderSlice<H, [T]>>を作成します.HeaderSliceは以下の様に定義されます.

pub struct HeaderSlice<H, T: ?Sized> {
    /// The fixed-sized data.
    pub header: H,

    /// The dynamically-sized data.
    pub slice: T,
}

Arc<HeaderSlice<H, [T]>>は以下の様な構造になります.

f:id:mm_i:20190214234050p:plain
Arc<HeaderSlice<H, [T]>>

これの何が嬉しいのかというと,zero-length arrayを受け取るCの関数をFFIで呼ぶ時なんかに便利かなと思います.servoではどう使われてるんでしょう(未確認)