Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

std::pin の勘所

835 views

Published on

Rust LT #3 のスライド

Published in: Technology
  • Be the first to comment

  • Be the first to like this

std::pin の勘所

  1. 1. 2019/03/25 Rust LT #3 後藤 弘明
  2. 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
  3. 3. The Rust logo is licensed under CC BY 4.0 (This is unofficial slide)  Future, Generator のためには自己参照する構造体を扱いたい  しかし、自己参照する構造体は Move すると不正な状態に陥る value pointer 元居た領域 value pointer ポインタが古い領域を示してしまう Move
  4. 4. 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) { ... } }
  5. 5. 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; こういった値の書き換えはできない
  6. 6. 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() } } }
  7. 7. 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());
  8. 8. 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>, } デバッグのためにつけた
  9. 9. 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 本当は みたいになって欲しい
  10. 10. 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禁止を強制したいけどどうすればいいんだ!
  11. 11. 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 されたオブジェクトは以下のように振る舞えば良い
  12. 12. 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を 安全に行うことが出来ないことを宣言
  13. 13. 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 } }
  14. 14. 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 } }
  15. 15. 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 を変更
  16. 16. 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; } }
  17. 17. 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; } }
  18. 18. 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());
  19. 19. 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

×