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

std::pin の勘所

  • 1.
    2019/03/25 Rust LT#3 後藤 弘明
  • 2.
    The Rust logois licensed under CC BY 4.0 (This is unofficial slide)  Rust 歴 4ヶ月ぐらい  OSS で regtail を開発中 https://github.com/StoneDot/regtail  某ソシャゲ会社でサーバーエンジニア  仕事ではPHPを使っている @StoneDot
  • 5.
    The Rust logois licensed under CC BY 4.0 (This is unofficial slide)  Future, Generator のためには自己参照する構造体を扱いたい  しかし、自己参照する構造体は Move すると不正な状態に陥る value pointer 元居た領域 value pointer ポインタが古い領域を示してしまう Move
  • 7.
    The Rust logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 logois 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 } }