Kanazawa.js 1.7 with Mozilla で利用したスライド
容量 10MB 制限があった時期に SpeakerDeck に UP していたもの:
http://speakerdeck.com/u/dynamis/p/kanazawajavascriptnext
2. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
Rust 歴 4ヶ月ぐらい
OSS で regtail を開発中
https://github.com/StoneDot/regtail
某ソシャゲ会社でサーバーエンジニア
仕事ではPHPを使っている
@StoneDot
5. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
Future, Generator のためには自己参照する構造体を扱いたい
しかし、自己参照する構造体は Move すると不正な状態に陥る
value pointer
元居た領域 value pointer
ポインタが古い領域を示してしまう
Move
7. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
大きさ2の配列を受け取り、いずれかの要素を選択する構造体
構造体作成時には0番目の要素を選択
toggle を使って選択要素を切り替え
get, set で値の取得と変更ができる
impl SelfRef {
pub fn new(ary: [i32; 2]) -> Pin<Box<SelfRef>> { ... }
pub fn toggle(self) { ... }
pub fn get(self) -> i32 { ... }
pub fn set(self, value: i32) { ... }
}
8. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
関数をまたがなければ参照で安全にできる(つまり使いものにならない)
参照における制約(1 mutable borrow)は引き継ぐ(Read Only ならば OK)
let mut ary = [1, 2];
let mut data = SelfRef { ary, ptr: &ary[0] };
data.ptr = &data.ary[0];
data.ptr = &data.ary[1];
#[derive(Debug)]
struct SelfRef<'a> {
ary: [i32; 2],
ptr: &'a i32,
}
*data.ptr = 10; こういった値の書き換えはできない
9. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
関数またいで使えないとかイケてないし、参照先も書き換えたい
Pointer 使えば良いじゃん!
unsafe だけどこれは流石に安全だよね?
#[derive(Debug)]
struct SelfRef {
ary: [i32; 2],
ptr: NonNull<i32>,
}
impl SelfRef {
fn get(self: &Self) -> i32 {
unsafe {
*self.ptr.as_ref()
}
}
}
10. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
全然安全ではありません! Moveした瞬間死にます
-919602312 コンストラクタで move するので即死
impl SelfRef {
fn new() -> SelfRef {
let mut data = SelfRef { ary: [1, 2], ptr: NonNull::dangling() };
data.ptr = NonNull::from(&data.ary[0]);
data
}
}
let mut data = SelfRef::new();
println!("{:?}", data.get());
11. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
BOX化すればアドレスは基本変わらないので、安全なはず!
impl SelfRef {
fn new(ary: [i32; 2]) -> Box<SelfRef> {
let data = SelfRef { ary, ptr: NonNull::dangling() };
let mut boxed = Box::new(data);
boxed.ptr = NonNull::from(&boxed.ary[0]);
boxed
}
}
struct SelfRef {
ary: [i32; 2],
pub ptr: NonNull<i32>,
}
デバッグのためにつけた
12. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
std::mem::swap, std::mem::replace みたいなことをされると死ぬ
let mut data1 = SelfRef::new([1, 2]);
let mut data2 = SelfRef::new([3, 4]);
std::mem::swap(data1.as_mut(), data2.as_mut());
unsafe { *data1.ptr.as_mut() = 100; }
println!("{} {}", data1.ary[0], data1.ary[1]);
println!("{} {}", data2.ary[0], data2.ary[1]);
3 4
100 2
data1 を書き換えたつもりが、data2 が変わっている
100 4
1 2
本当は みたいになって欲しい
13. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
参照を使うとデータの中身を変化させられない&関数跨げない
1 mutable borrow の制約からの自然な帰結
関数を跨げないので基本的に使い物にならない
NonNull<T>で自己参照できるけど……
Moveされるだけで簡単にダングリングポインタになっちゃう
Box化すればヒープにいるから基本大丈夫だけど、std::mem::swapとかで死ぬ
&mut が取り出せちゃうと move, swap などを止めることが出来ないのが最大の原因
Move禁止を強制したいけどどうすればいいんだ!
15. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
Pinされたオブジェクトの mutable reference の取得は安全でない!
なぜなら std::mem::swap で move される危険があるため
Pinされたオブジェクトは、
unsafeコード以外からは mutable reference を取得できない
safe関数でラップして通常操作での安全性を担保する
immutable reference の取得は問題なく行える
immutable reference からは move をすることができないため
つまり Pin されたオブジェクトは以下のように振る舞えば良い
16. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
Pinしていても自由にMoveできる構造体には Unpin (auto-trait) がついている
普通に構造体を作ると Unpin が勝手につくので Move 可能な構造体として振る舞う
get_mut() で mutable reference が取れてしまう
扱う構造体が move に対して安全じゃない場合は構造体に
std::marker::PhantomPinned を持たせることで Unpin を opt-out する必要がある
struct SelfRef {
ary: [i32; 2],
ptr: NonNull<i32>,
_pin: PhantomPinned
}
SelfRefはPinされているときにMoveを
安全に行うことが出来ないことを宣言
17. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
Box::pin, Rc::pin, Arc::pin でPINされたスマートポインタを作れる
impl SelfRef {
fn new(ary: [i32; 2]) -> Pin<Box<SelfRef>> {
let data = SelfRef { ary, ptr: NonNull::dangling(), _pin: PhantomPinned };
let mut boxed = Box::pin(data);
let ptr = NonNull::from(&boxed.ary[0]);
unsafe {
boxed.as_mut().get_unchecked_mut().ptr = ptr;
}
boxed
}
}
18. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
unsafe コードを通してデータを変更
impl SelfRef {
fn new(ary: [i32; 2]) -> Pin<Box<SelfRef>> {
let data = SelfRef { ary, ptr: NonNull::dangling(), _pin: PhantomPinned };
let mut boxed = Box::pin(data);
let ptr = NonNull::from(&boxed.ary[0]);
unsafe {
boxed.as_mut().get_unchecked_mut().ptr = ptr;
}
boxed
}
}
19. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
unsafe部分を分解して、型を追いかける
unsafe {
boxed.as_mut().get_unchecked_mut().ptr = ptr;
}
unsafe {
let mut_ref = boxed.as_mut();
let mut_obj = mut_ref.get_unchecked_mut();
mut_obj.ptr = ptr;
}
等価な変換
Pin<Box<SelfRef>>を
Pin<&mut SelfRef> に変換
&mut SelfRef に変換 (unsafe)
&mut SelfRef を変更
20. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
get は deref が効くので self をとって処理をすることができる
set は &mut Pin<Box<Self>> が必要なのでselfを使ったメソッド構文が使えない
Pin<Box<Self>>, Arc<Self> などはできるようになってます (v1.33)
arbitrary_self_types を使えばできる (nightly)
pub fn get(&self) -> i32 {
unsafe { *self.ptr.as_ref() }
}
pub fn set(target: &mut Pin<Box<Self>>, value: i32) {
unsafe { *target.as_mut().get_unchecked_mut().ptr.as_mut() = value; }
}
21. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
setと同じくPin<Box<Self>>が必要なので、メソッド呼び出し構文は使用不可
pub fn toggle(target: &mut Pin<Box<Self>>) {
let ptr;
if NonNull::from(&target.ary[0]) == target.ptr {
ptr = NonNull::from(&target.ary[1]);
} else {
ptr = NonNull::from(&target.ary[0]);
}
unsafe {
target.as_mut().get_unchecked_mut().ptr = ptr;
}
}
22. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
SelfRef::set, SelfRef::toggle は通常の関数呼び出しを使う必要がある
struct ToggleInt { inner: Pin<Box<SelfRef>> } のように別構造体で包めば、
使いやすいインターフェースを提供することも可能
let mut data = SelfRef::new([1, 2]);
assert_eq!(1, data.get());
SelfRef::set(&mut data, 100);
assert_eq!(100, data.get());
SelfRef::toggle(&mut data);
assert_eq!(2, data.get());
SelfRef::toggle(&mut data);
assert_eq!(100, data.get());
23. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)
std::pin を使うと move 不可なオブジェクトが作れる
std::pin を使うのに unsafe コードは必須
必要性が薄いなら別実装を考えたほうが良いかも
Pin<Box<T>> を生で扱うとインターフェースが汚くなるので注意
Pin<Box<T>> ならメソッドレシーバーにできるけど、&mut Pin<Box<T>> とかはまだ無
理
キレイなインターフェース版は GitHub においておきます
https://github.com/StoneDot/toggle-int
Editor's Notes
#10: impl SelfRef { fn new() -> SelfRef { let mut ary = [1, 2]; let mut data = SelfRef { ary, ptr: NonNull::dangling() }; data.ptr = NonNull::from(&ary[0]); data }}