こんにちは。 先日、しばらく不動の一位を守ってきたRustをVSCodeで使う記事を抜き、 私の書いた非同期プログラミングの記事の記事が一番人気になったと思いきや数日でまた抜き返されて傷心中の、 R&Dチームの齋藤(@aznhe21)です。
さて、Rustの非同期プログラミングで時々Pin
を使ったり、コンパイラにUnpin
が不足していると怒られたりしませんか?
そんな時によく分からずuseしたり別の手段を取ったりしていませんか?
今回、このままではマズいと思ってPin
を勉強して完全に理解しましたので、その成果を皆さんと共有したいと思います。
更新履歴
- 03/10
- 指摘を受け下記2点を修正しました
Unpin
を実装しない型もムーブ出来ることへの言及- pin-projectクレートが安全であることによる書き換え
- 指摘を受け下記2点を修正しました
対象読者
この記事は下記全てに当てはまる人を想定して執筆しています。
- Rustのトレイトを使える
std::pin::Pin
を詳しく知りたい- 変数がムーブしたとき、その変数のアドレスが変わることを理解している
- スタックやヒープが何であるかを知っている
つまり・・・どういうことだってばよ(TL;DR)
Pin
型の変数は、それ自体をムーブしても内部に保持するポインタのアドレス\は変わらない- この性質を利用すれば自己参照構造体を安全に取り扱うことが出来る
- 値の変更は内部フィールドのアドレスが変わる可能性があるため、
Pin<P>
から可変参照を得るためにはP
が、 「変更しても安全」であることを示すUnpin
トレイトを満たす必要がある
- 更新履歴
- 対象読者
- つまり・・・どういうことだってばよ(TL;DR)
- Pinを使うその前に
- Pinの使い方
- Pinをうまく使うためのクレート
- Pinは非同期プログラミングより
- さいごに
- 謝辞
- ライセンス表記
Pinを使うその前に
まずはよく一緒に使われる非同期プログラミングとは切り離し、Pin
とUnpin
がどういった部分で必要になるかを説明しましょう。
なぜPin
が必要になるのか
まず、Pin
の仕組みは値をムーブさせたくないときに必要になるものです
「値をムーブさせたくないとき」というのは、主に自己参照構造体を使うときのことを言います。
struct SelfRef { x: u32, // ptrは常にxを指していて欲しいが、SelfRefがムーブした瞬間に別のアドレスを指すようになる ptr: *const u32, } impl SelfRef { pub fn new(x: u32) -> SelfRef { let mut this = SelfRef { x, ptr: std::ptr::null(), }; this.ptr = &this.x; // まだアドレスは変わらないのでテストは成功する assert_eq!(&this.x as *const _, this.ptr); // ここで値を返した瞬間にxのアドレスが変わり、ptrの値が不正となる this } } fn main() { let v = SelfRef::new(0); // v.xとv.ptrの値が異なるためテスト失敗 assert_eq!(&v.x as *const _, v.ptr); }
このとき、SelfRef
のptr
は常にx
へのアドレスを保持していて欲しいわけですが、
コンストラクタから値を返した時点で変数のアドレスが変わってしまうためこのコードはうまく動きません。
このような「ムーブしたら絶対アカン😡型」をうまく使うために、Pin
とUnpin
という仕組みが用意されています。
ムーブしてもおけまる🙆な型のためのUnpin
トレイト
Pin
について説明する前に、まず理解しなければならないのがUnpin
トレイトです。
このUnpin
トレイトは「ムーブしてもおけまる🙆な型」に実装されます。
// Unpinの定義はこれっぽっち pub auto trait Unpin {}
このUnpin
トレイトは自動トレイト*1として宣言されており、基本的にはあらゆる型に実装されます。
それもそのはず、普通にコードを書いていて「ムーブしたら絶対アカン😡型」なんてものは出てこないからです。
そのため、先述したSelfRef
といった稀有な「ムーブしたら絶対アカン😡型」には自分で「ムーブしたら絶対アカン😡型マーク」を付ける必要があります。
これにはstd::marker::PhantomPinned
を使います。
use std::marker::PhantomPinned; struct SelfRef { x: u32, ptr: *const u32, _pinned: PhantomPinned, }
PhantomPinned
はUnpin
を実装しない型です。
そして、自動トレイトの機能によって「Unpin
を実装しない型を含む型」もまた「Unpin
を実装しない型」となります。
これによってSelfRef
は「ムーブしてもおけまる🙆な型」ではなく「ムーブしたら絶対アカン😡型」を自称することが出来るようになりました。
ここで「自称」と書いたのには意味があります。
Unpin
トレイトを付けないようにしたとしても、コンパイラが特別扱いしてくれるわけではないのです。
つまり、SelfRef
を「ムーブしたら絶対アカン😡型」といくら「自称」したところで、いくらでもムーブが出来てしまうのです。
use std::marker::PhantomPinned; struct Obj { _pinned: PhantomPinned, } fn move_obj(_obj: Obj) { println!("objがムーブされた"); } fn main() { let obj = Obj { _pinned: PhantomPinned }; move_obj(obj); }
では「ムーブしたら絶対アカン😡型」をムーブさせないようにするためにはどうしたら良いのでしょう。
オブジェクトがムーブしないのはどういうときか
そもそも「オブジェクトがムーブしない」というのは一体どんなときでしょうか?
答えは2つあります。 1つは「スタックから移動しない変数」で、もう1つは「ヒープに確保された変数」です。
スタックから移動しない変数
いつものように変数を定義するとスタックにその領域が確保されるわけですが、
スタックに確保された変数が別の場所に移動することはSelfRef
の例で述べた通りです。
しかし、スタックに確保された変数を移動できないようにする方法があります。 コードを見てみましょう。
// (1) 変数xを定義 let mut x = Object::new(); // (2) xへの参照をxとして定義 let mut x = &mut x;
(1)でx
を普通の変数として定義しています。これは何の変哲もない定義です。
ここのミソは(2)にあり、(1)で定義した名前と同じx
という名前で、(1)のx
への参照を定義しています。
これにより、(1)で定義したx
にアクセスは出来なくなり、「別の場所に移動しない変数」となったわけです。
すなわち、もはや(1)のx
のアドレスは変わることがないのです。
このようにしてObject
インスタンスへのアドレスが変わらないように、つまり「オブジェクトがムーブしない」ようになるのです。
ヒープに確保された変数
スタックではなくヒープに確保された変数もまた、ムーブしない変数です。
// 変数をヒープに確保 let x = Box::new(Object::new());
このようにしてObject
インスタンスをヒープに確保すればObject
インスタンそのアドレスが変わらないように、
つまり「オブジェクトがムーブしない」ようになります。
と思うじゃん?
ここまで述べてきてちゃぶ台をひっくり返す(ノ`Д´)ノ彡┻━┻ようですが、実はムーブしないオブジェクトをムーブさせる抜け道があります。
その抜け道とはstd::mem::replace
やstd::mem::swap
などの&mut T
を受け取り、内部を丸ごと書き換える関数です。
これらの関数を使うと変数の中身をそのまま取り出すことが出来ます。 つまりインスタンスをそのままに「オブジェクトがムーブする」ことになってしまうのです。
そのコードを実際に見てみましょう。
// xの中身はもちろんx let mut x = Object::new(); // 変数xを隠蔽することによってムーブを防ぐ let mut x = &mut x; // 隠蔽したxの「中身」を取り出す let y = std::mem::replace(x, Object::new()); // xの中身がyにムーブしてしまった
// Objectをヒープに確保することによってムーブを防ぐ let mut x = Box::new(Object::new()); // ヒープにあるObjectをスタックに引っ張り出す let y = std::mem::replace(&mut *x, Object::new()); // xの中身がyにムーブしてしまった
このコードにより、一度は隠蔽したx
の中身やヒープに確保したx
がy
にムーブされてしまいます。
これは由々しき事態です。
Pinがムーブを阻止する
可変参照を使いつつも値のムーブを防ぐための仕組みがPin
です。
では、Pin
はどのようにしてムーブさせない仕組みを提供しているのでしょうか。
まずはPin
の定義を見てみます。関数定義を除けばこのようになっています。
pub struct Pin<P> { /* fields omitted */ } impl<P: Deref> Pin<P> where P::Target: Unpin { // ... } impl<P: Deref> Pin<P> { // ... } impl<P: DerefMut> Pin<P> { // ... } impl<'a, T: ?Sized> Pin<&'a T> { // ... } impl<'a, T: ?Sized> Pin<&'a mut T> { // ... }
型P
はポインタ(Pointer)のPを表しており、実質的に P
にはDeref
を実装したポインタ型のみが入る制限があります。
より詳しく言うと、実装から型P
にはDeref
かDerefMut
、あるいは&T
、&mut T
が来ることが分かります。
DerefMut
はDeref
を要求し、また&T
や&mut T
はそれぞれDeref
とDerefMut
を満たすため、
実質的に Pin
はDeref
を要求しているのです
Pin
がDeref
を要求するということは、Pin
は逆参照の出来る型、つまり&T
やBox<T>
などを要求します。
そして、これらの型をPin
で包むと、Unpin
を実装しない「ムーブしたら絶対アカン😡型」でのDerefMut
の使用を制限します。
DerefMut
が使えないということはPin
を通して変数を可変参照として扱えないということです。
変数を可変参照で扱えると、以前の章で説明した通りstd::mem::replace
などによって変数の中身を取り出すことが出来ます。
逆に言えば可変参照として扱えないのなら変数のムーブを防ぐことが出来るのです。
Pin
はDerefMut
の使用を制限しますから、翻って DerefMut
の使えないPin
は変数のムーブを防ぐことが出来るというわけです。
ここでPin
のDeref
とDerefMut
の実装を見てみましょう*2。
これを見るとPin
のDerefMut
は「ムーブしてもおけまる🙆な型」(Unpin
を実装した型)でのみ提供され、
「ムーブしたら絶対アカン😡型」には提供されないことが分かります。
// Derefはどんな型にも提供する impl<P: Deref> Deref for Pin<P> { type Target = P::Target; fn deref(&self) -> &P::Target { /* implementation omitted */ } } // 「ムーブしてもおけまる🙆な型」にのみDerefMutを提供する impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { fn deref_mut(&mut self) -> &mut P::Target { /* implementation omitted */ } }
ただしDerefMut
が提供されないということはその中身を変更することが出来ないということでもあります。
もちろんその手段は用意されていますので後程ご紹介します。
Pinの使い方
ここまででPin
の必要性が分かりました。それではPin
を実際に使っていきましょう。
Pinの作り方
Pin
にはコンストラクタが2つあります。しかし、Pin
の生成にはどちらも使いません。
その理由を説明するために、まずはその2つのコンストラクタを紹介しましょう*3。
impl<P: Deref> Pin<P> where P::Target: Unpin { pub fn new(pointer: P) -> Pin<P> { /* implementation omitted */ } } impl<P: Deref> Pin<P> { pub unsafe fn new_unchecked(pointer: P) -> Pin<P> { /* implementation omitted */ } }
Pin::<P>::new
はP
がDeref
であり、かつDeref::Target
がUnpin
を実装する場合のみ使うことができるもので、
逆にPin::<P>::new_unchecked
は不安全ですが使用に制限がないものです。
つまり、Pin
は「ムーブしたら絶対アカン😡型」をムーブさせないためのものなのに、
Pin
を作るためには「ムーブしてもおけまる🙆な型」であることが要求されるのです。
となると、「ムーブしたら絶対アカン😡型」をPin
で扱うためには不安全な方のコンストラクタを使うことになるわけです。
ではPin
を作るには不安全なコードを書かなければならないのでしょうか?
いいえ、安心してください。不安全なコンストラクタは不安全なコードのためのものであり、
安全なコードを書く上では別の機能が用意されています。つまり、Pin
の2つのコンストラクタはどちらも使わないのです。
安全Pin
Pin
を安全なコードから生成するための仕組みはいくつか用意されています。
これらの仕組みは全てスタックかヒープに変数を固定し、Pin
にピン留めする機能を持っています。
ここでは変数をメモリに固定した上でPin
に包むことを「スタックでピン留め」あるいは「ヒープでピン留め」と呼んでいます。
pin_utils::pin_mut!マクロ
pin-utilsクレートのpin_mut!
マクロは「スタックから移動しない変数」で紹介したコードに相当する内容を安全に実現します。
このマクロは以下のように使います。
use std::marker::PhantomPinned; use std::pin::Pin; use pin_utils::pin_mut; // Unpinを実装しない型 struct NotUnpin { _pinned: PhantomPinned, } impl NotUnpin { // NotUnpinのインスタンスを生成する pub fn new() -> NotUnpin { NotUnpin { _pinned: PhantomPinned, } } // Pin<&mut Self>をselfとして受け取る pub fn method(self: Pin<&mut Self>) { println!("やあやあ我こそはPinなり!"); } } // 値がPinで包まれているかをコンパイル時に確認するためのダミー関数 fn assert_pin<T>(_: &Pin<&mut T>) {} fn main() { let obj = NotUnpin::new(); // objはUnpinを実装していないためPin::newを使えない // let obj = Pin::new(obj); // pin_mutによってスタックでピン留めする pin_mut!(obj); // objはPin<&mut NotUnpin>である assert_pin::<NotUnpin>(&obj); // Pinになったのでメソッドを呼び出せる // obj.method()でも呼び出せるがobjを消費してしまって2度目以降の呼び出しが出来なくなるためas_mut()を通して呼び出す obj.as_mut().method(); obj.as_mut().method(); }
このマクロによって安全に、変数obj
をスタックに固定しつつPin
に包み、ピン留めすることが出来ます。
tokio::pin!マクロ
pin_utils::pin_mut!マクロと同様の機能がtokioクレートにも用意されています。
このマクロはpin_mut!
のような定義済みの変数をPin
に包む機能に加え、
変数をその場でPin
に包んで定義する機能もあります*4。
ただし、このためだけのtokioを使うのは流石にクレートとしての規模が大きすぎるので、
tokioを使っているならpin-utilsの代わりに使う、程度の感覚で良いでしょう。
use std::marker::PhantomPinned; use std::pin::Pin; use tokio::pin; // Unpinを実装しない型 struct NotUnpin { _pinned: PhantomPinned, } impl NotUnpin { // NotUnpinのインスタンスを生成する pub fn new() -> NotUnpin { NotUnpin { _pinned: PhantomPinned, } } // Pin<&mut Self>をselfとして受け取る pub fn method(self: Pin<&mut Self>) { println!("やあやあ我こそはPinなり!"); } } fn main() { // pin_utils::pin_mutと同じ使い方 { let obj = NotUnpin::new(); pin!(obj); obj.as_mut().method(); } // その場で変数の宣言も出来る { pin! { let obj = NotUnpin::new(); } obj.as_mut().method(); } }
てんかいごのすがた
では、このマクロはどのように展開されるのでしょうか? 結論から言えば「スタックから移動しない変数」で書いたコードと同じようなコードに展開されます。
具体的には下記で示すようなコードに展開されます。
use std::marker::PhantomPinned; use pin_utils::pin_mut; // Unpinを実装しない型 struct NotUnpin { _pinned: PhantomPinned, } impl NotUnpin { // NotUnpinのインスタンスを生成する pub fn new() -> NotUnpin { NotUnpin { _pinned: PhantomPinned, } } } // マクロを使ったコード fn use_macro() { // 0. objを宣言 let obj = NotUnpin::new(); // 1〜3. objをPin化 pin_mut!(obj); } // マクロが展開されるとこのようになる。マクロであるため少し複雑なコードとなる fn expanded() { // 0. objを宣言 let obj = NotUnpin::new(); // 3. 元の変数と同じ名前で、Pin化した変数を宣言 let mut obj = { // 1. objを可変にするためにmutな変数に代入 let mut obj = obj; // 2. 可変参照を取ってスタックに固定し、スタックから移動しないようにする // このコードは安全であるため、unsafeブロックで囲んでも問題ない unsafe { Pin::new_unchecked(&mut obj) } }; } // マクロで展開されたコードから無駄な部分を省くと以下のコードとなる fn simplified() { // 0〜1. objをmutで宣言 let mut obj = NotUnpin::new(); // 2〜3. 可変参照を取ってスタックに固定し、元の変数と同じ名前でPin化した変数を宣言 // このコードは安全であるため、unsafeブロックで囲んでも問題ない let mut obj = unsafe { Pin::new_unchecked(&mut obj) }; }
Box::pin
Box::pin
は変数をヒープに確保・固定すると同時にPin
に包んでくれます。
つまり「ヒープに確保された変数」となるわけであり、すなわち「ヒープでピン留めする」のです。
use std::marker::PhantomPinned; use std::pin::Pin; // Unpinを実装しない型 struct NotUnpin { _pinned: PhantomPinned, } impl NotUnpin { // NotUnpinのインスタンスを生成する pub fn new() -> NotUnpin { NotUnpin { _pinned: PhantomPinned, } } // Pin<&mut Self>をselfとして受け取る pub fn method(self: Pin<&mut Self>) { println!("やあやあ我こそはPinなり!"); } } fn main() { let obj = NotUnpin::new(); // objはUnpinを実装していないためPin::newを使えない // let obj = Pin::new(obj); // Box::pinによってヒープでピン留めする let mut obj: Pin<Box<NotUnpin>> = Box::pin(obj); // Pinになったのでメソッドを呼び出せる // selfの型をPin<Box<Self>>ではなくPin<&mut Self>にしているため、obj.method()として呼び出せない // 代わりにPin::as_mutを使いPin<Box<T>>からPin<&mut T>に変換して呼び出す obj.as_mut().method(); }
Rc::pin / Arc::pin
Rc::pin
及びArc::pin
は、Box::pin
と同じく変数をヒープに固定してPin
でピン留めします。
参照カウントが必要な場面で使うことになるでしょう。
使い方はBox::pin
と同じです。
ムーブしたら絶対アカン😡型も最初だけはムーブして良い
ここでよく考えてみると、上記のいずれの場合でもコンストラクタの戻り値がムーブしていることに気付きます。
// NotUnpin::newからobjにムーブしている let mut obj = NotUnpin::new(); let mut obj = unsafe { Pin::new_unchecked(&mut obj) }; // NotUnpin::newからBox::newにムーブしている let obj = Box::new(NotUnpin::new());
と言う事は、「ムーブしたら絶対アカン😡型」をムーブしていることになります。
実のところ、「ムーブしたら絶対アカン😡型」は「『ピン留めされたあとに』ムーブしたら絶対アカン😡型」なのです。 逆に言えば「『ピン留めされるまでは』ムーブしてもおけまる🙆な型」でもあります。
そもそも、Unpin
トレイトは名前からすれば「ピン留めを外せることを示す」トレイトだと言えます。
つまりピン留めを前提にしているのです。
なので、Unpin
を実装する型は「ピン留めを外しても良い」、
つまり「『ピン留めされたあとであっても』ムーブしてもおけまる🙆な型」を示すトレイトなわけであり、
Unpin
を実装しない型は「ピン留めを外してはいけない」、
つまり「『ピン留めされたあとに』ムーブしたら絶対アカン😡型」型なのです。
そのため、「ムーブしたら絶対アカン😡型」は最初はムーブされても問題ない様に作る必要があります。 例えば自身のフィールドへのポインタを保持する構造体では、最初はnullポインタを入れておき、 そのフィールドについてはいつでもnullを処理できるようにしておきます*5。
なお、この記事では説明を簡単にするため、オブジェクトの生成部分を無視し、
Unpin
を実装しない型を「ムーブしたら絶対アカン😡型」としています。
Pinをselfとして受け取る
安全Pinの章でしれっとfn method(self: Pin<&mut Self>)
という構文が出て違和感を覚えた人もいるかと思います。
良く使う構文はfn method(&self)
のような形ですよね。
実は良く使う構文もfn method(self: &Self)
への糖衣構文なのです。この構文と見比べれば違和感は薄れると思います。
self
をPin
として受け取った場合、それはピン留めされた参照であるため、
その中身は構造体の外ではムーブされないことになります。
Pin<&mut Self>
は自身を変更できるピン留めされた参照ですし、
他にもPin<&Self>
という構文をピン留めされた不変参照として使うことも出来ます。
なお、不変参照であればDeref
を通して&self
を受け取るメソッドをそのまま呼び出すことも出来ますが、
こちらはどこかでムーブされるかもしれないメソッドであるため、&self
とPin<&Self>
は慎重に使い分けましょう。
ところで、全ての型がself
の型として扱えるわけではありません。
Rust 1.41現在ではself
の型として扱えるのは下記に限られています。
Self
&mut Self
&Self
Box<Self>
Rc<Self>
Arc<Self>
Pin<T>
因みにPin<T>
のT
は上記におけるSelf
以外の「self
の型として扱えるもの」が要求されるため、無駄にネストすることもできます。
もちろん用途はありません。
use std::pin::Pin; struct Hoge; impl Hoge { // 全部OK fn method1(self: Pin<&mut Self>) {} fn method2(self: Pin<&Self>) {} fn method3(self: Pin<Box<Self>>) {} fn method4(self: Pin<Pin<Pin<Pin<&mut Self>>>>) {} }
なお、将来的には様々な型をself
として受け取れるようになる可能性があります。
Pinの中身を変更する
さて、Pin
を作ったり受け取ったりは出来るようになりましたが、今のところ受け取ったとしても中身を変更することができません。
これでは不変参照を使うのと変わらないどころかタイプ数が無駄に増えているだけです。
でも大丈夫。中身を変更するAPIもしっかり用意されています。
変数の中身を書き換えるには不安全なPin::get_unchecked_mut
を使うこととなります。
これは「ムーブしたら絶対アカン😡型」を書き換えた場合にオブジェクトの内部状態がどのようになるかはコンパイラは分からないためであり、
内部状態の整合性が保たれることに実装者が責任を持つ必要があります。
use std::pin::Pin; struct Hoge { field: u32, } impl Hoge { fn get(self: Pin<&mut Self>) -> u32 { // 中身を変更しない場合、Derefによって透過的にフィールドにアクセスできる self.field } fn inc(self: Pin<&mut Self>) { unsafe { // Pin::get_unchecked_mutから&mut Hogeが返る let this: &mut Hoge = self.get_unchecked_mut(); this.field += 1; } } } fn main() { let mut hoge = Box::pin(Hoge { field: 0 }); hoge.as_mut().inc(); assert_eq!(hoge.as_mut().get(), 1); }
安全にPinの中身を変更する
更新(03/09):指摘を受け、#pin_project属性マクロを使う前提に書き換えました
不安全なコードでPin
の中身を変更できるようにはなりましたが、使う場面でいちいちunsafeブロックが必要になり、
しかも本当にそのコードが安全なのか不安になります。
でも安心してください。この操作を安全に実現するクレートがあります。
pin_project::pin_project属性マクロ
pin-projectクレートが提供する#[pin_project]属性マクロを使うと、
安全にPin<&mut Self>
からフィールドを&mut T
として取り出すことが出来るため、安全にPin
の中身を変更することが出来ます。
use std::pin::Pin; use pin_project::pin_project; // 構造体定義に#[pin_project]属性マクロを付ける #[pin_project] struct Hoge { field: u32, } impl Hoge { fn get(self: Pin<&mut Self>) -> u32 { // 中身を変更しない場合、Derefによって透過的にフィールドにアクセスできる self.field } fn inc(self: Pin<&mut Self>) { // projectメソッドはHogeのfieldフィールドが&mut u32になった構造体を返す let this = self.project(); *this.field += 1; } } fn main() { let mut hoge = Box::pin(Hoge { field: 0 }); hoge.as_mut().inc(); assert_eq!(hoge.as_mut().get(), 1); }
ちょっと危険なpin_utils::unsafe_unpinned!マクロ
pin-utilsクレートが提供するunsafe_unpinned!
マクロは、Pin<&mut Self>
からフィールドを&mut T
で取り出すメソッドを定義します。
ただし名前にunsafeと入っていることから分かる通りこのマクロは安全ではなく、Pin::get_unchecked_mut
を使うときと同様、
内部状態の整合性が保たれることに実装者が責任を持つ必要があります。
use std::pin::Pin; use pin_utils::unsafe_unpinned; struct Hoge { field: u32, } impl Hoge { unsafe_unpinned!(field: u32); fn get(self: Pin<&mut Self>) -> u32 { // 中身を変更しない場合、Derefによって透過的にフィールドにアクセスできる self.field } fn inc(self: Pin<&mut Self>) { // self.field()は&mut u32を返す let field: &mut u32 = self.field(); *field += 1; } } fn main() { let mut hoge = Box::pin(Hoge { field: 0 }); hoge.as_mut().inc(); assert_eq!(hoge.as_mut().get(), 1); }
PinのフィールドからPinを生成する
前の章ではPin
で受け取った自身のフィールドを書き換える方法をご紹介しました。
では、自身のフィールドにPin
を要求するオブジェクトがあった場合にはどうなるでしょうか。
以下の場合を考えてみます。
use std::pin::Pin; struct Hoge {} impl Hoge { pub fn hoge(self: Pin<&mut Self>) {} } struct Fuga { hoge: Hoge, } impl Fuga { pub fn fuga(self: Pin<&mut Self>) { unsafe { let this = self.get_unchecked_mut(); // Hoge::hogeを呼び出したいが、this.hogeはHoge型なのでPin<&mut Hoge>として受け取れない this.hoge.hoge(); } } }
このようなパターンは現実にはFuture
を使うときに良く出てきます。
これは標準ライブラリにおいてはPin::map_unchecked_mut
を使うことで実現できます。
このメソッドはPin::get_unchecked_mut
と同じく不安全なメソッドであり、
例によって内部状態の整合性が保たれることに実装者が責任を持つ必要があります。
use std::pin::Pin; struct Hoge {} impl Hoge { pub fn hoge(self: Pin<&mut Self>) { println!("ほげ"); } } struct Fuga { hoge: Hoge, } impl Fuga { pub fn fuga(self: Pin<&mut Self>) { unsafe { // map_unchecked_mutによりhogeをPin<&mut Hoge>として取り出すことが出来る let mut hoge: Pin<&mut Hoge> = self.map_unchecked_mut(|this| &mut this.hoge); hoge.as_mut().hoge(); } } } fn main() { let mut fuga = Box::pin(Fuga { hoge: Hoge {} }); fuga.as_mut().fuga(); }
安全にPinのフィールドからPinを生成する
更新(03/09):指摘を受け、#pin_project属性マクロを使う前提に書き換えました
前章と同じく、外部クレートを使うことで安全にこの操作を実現できます。
pin_project::pin_project属性マクロ
pin-projectクレートが提供する[#[pin_project]]属性マクロと#[pin]
ヘルパー属性を使うと、
安全にPin<&mut Self>
からフィールドをPin<&mut U>
として取り出すことができ、安全にPin<&mut Self>
からPin
を生成することが出来ます。
use std::pin::Pin; use pin_project::pin_project; struct Hoge {} impl Hoge { fn hoge(self: Pin<&mut Self>) { println!("ほげ"); } } // 構造体定義に#[pin_project]属性マクロを付ける #[pin_project] struct Fuga { // Pin化したいフィールドに#[pin]ヘルパー属性を付ける #[pin] hoge: Hoge, } impl Fuga { fn fuga(self: Pin<&mut Self>) { // projectメソッドはFugaのhogeフィールドがPin<&mut Hoge>になった構造体を返す let this = self.project(); this.hoge.hoge(); } } fn main() { let mut fuga = Box::pin(Fuga { hoge: Hoge {} }); fuga.as_mut().fuga(); }
ちょっと危険なpin_utils::unsafe_pinned!マクロ
pin-utilsクレートが提供するunsafe_pinned!
マクロは、Pin<&mut Self>
からフィールドをPin<&mut T>
で取り出すメソッドを定義します。
ただし名前にunsafeと入っていることから分かる通りこのマクロは安全ではなく、Pin::map_unchecked_mut
を使うときと同様、
内部状態の整合性が保たれることに実装者が責任を持つ必要があります。
use std::pin::Pin; use pin_utils::unsafe_pinned; struct Hoge {} impl Hoge { fn hoge(self: Pin<&mut Self>) { println!("ほげ"); } } struct Fuga { hoge: Hoge, } impl Fuga { unsafe_pinned!(hoge: Hoge); fn fuga(self: Pin<&mut Self>) { // self.hoge()はPin<&mut Hoge>を返す self.hoge().hoge(); } } fn main() { let mut fuga = Box::pin(Fuga { hoge: Hoge {} }); fuga.as_mut().fuga(); }
サンプル:サイズが静的な、自己参照する配列型
これはVec
と違ってヒープを使わず、メモリ領域が静的に決定される配列型です。
ここではサンプルのため無理矢理tail
として自己参照していますが、実際にはusize
で配列の長さを保持するだけで実現できます。
なお、このコードはヒープを利用しておらず、また標準ライブラリのみで動きます。
use std::marker::PhantomPinned; use std::mem::{self, MaybeUninit}; use std::pin::Pin; use std::ptr::NonNull; // メモリ領域が静的なサイズの配列型。 struct Array<T> { // 配列の実データ array: [MaybeUninit<T>; 1024], // arrayの末尾要素を指す。長さを保持する代わりに自身への参照を保持する // 初期状態ではNoneを保持する tail: Option<NonNull<T>>, _pinned: PhantomPinned, } impl<T> Array<T> { // Arrayを新しく生成する pub fn new() -> Array<T> { unsafe { Array { // [MaybeUninit::uninit(); 1024]は出来ないのでこうする array: MaybeUninit::uninit().assume_init(), // 最初はムーブしても良いようにNoneとする tail: None, _pinned: PhantomPinned, } } } // 配列の長さを返す // このメソッドはselfにムーブされ得ないことを要求する pub fn len(self: Pin<&Self>) -> usize { match &self.tail { Some(tail) => tail.as_ptr() as usize + 1 - self.array.as_ptr() as usize, None => 0, } } // 配列のindex番目から読み込む pub fn read(self: Pin<&Self>, index: usize) -> Option<&T> { if index < self.len() { unsafe { Some(&*self.array[index].as_ptr()) } } else { None } } // 配列にデータを追加する。データを追加できなかった場合はfalseを返す pub fn push(self: Pin<&mut Self>, x: T) -> bool { let len = self.as_ref().len(); if len >= 1024 { return false; } // pushは内部状態を変化させるため不安全なのは当然 unsafe { let this = self.get_unchecked_mut(); // 末尾要素へのポインタを取得 let tail = this.array[len].as_mut_ptr(); // 未初期化の値をdropしないようにしつつ追加要素を書き込む tail.write(x); // tailを更新し、tailが常にarrayの末尾要素を指すようにする this.tail = Some(NonNull::new_unchecked(tail)); true } } // 配列のindex番目に書き込む pub fn write(self: Pin<&mut Self>, index: usize, x: T) { assert!(index < self.as_ref().len()); // writeは内部状態を変化させないにも関わらずget_unchecked_mutによる不安全なコードになってしまう unsafe { let this = self.get_unchecked_mut(); let ptr = this.array[index].as_mut_ptr(); // 以前書き込まれた値をdropし、新しい値を書き込む *ptr = x; } } // 末尾要素を取り出す pub fn pop(self: Pin<&mut Self>) -> Option<T> { unsafe { let this = self.get_unchecked_mut(); if let Some(tail) = this.tail { let tail = tail.as_ptr(); // 末尾要素を読み出して戻り値とする let v = tail.read(); // 末尾要素を1つ戻す let tail = tail.sub(1); // tailが先頭より前に行かないようにする this.tail = if tail >= this.array[0].as_mut_ptr() { Some(NonNull::new_unchecked(tail)) } else { None }; Some(v) } else { None } } } // PinとしてDropする fn drop_pinned(self: Pin<&mut Self>) { unsafe { let len = self.as_ref().len(); let this = self.get_unchecked_mut(); if mem::needs_drop::<T>() { // 各要素はMaybeUninitのためdropされないので自分でdropを実行する for i in 0..len { this.array[i].as_mut_ptr().drop_in_place(); } } this.tail = None; } } } impl<T> Drop for Array<T> { fn drop(&mut self) { unsafe { // DropするときはPinとしてDropする Pin::new_unchecked(self).drop_pinned(); } } } fn main() { // Arrayを生成し、スタックに固定、ピン留めする let mut array = Array::new(); // pin_utils::pin_mut!(obj);でも可 let mut array = unsafe { Pin::new_unchecked(&mut array) }; // 値をpushしてみる array.as_mut().push(0u32); assert_eq!(array.as_ref().len(), 1); assert_eq!(array.as_ref().read(0), Some(&0u32)); // 値を書き換えてみる array.as_mut().write(0, 1u32); assert_eq!(array.as_ref().read(0), Some(&1u32)); assert_eq!(array.as_mut().pop(), Some(1u32)); // 値をpopしたので要素は空 assert_eq!(array.as_ref().len(), 0); }
Pinのメソッド一覧
Pin
の使い方が分かったところで、Rust 1.41時点のPin
のメソッド全てを紹介しましょう。
Pin::new
impl<P: Deref> Pin<P> where P::Target: Unpin { pub fn new(pointer: P) -> Pin<P>; }
型P
がDeref
を、P::Target
がUnpin
を実装している場合にのみ使えるメソッドです。
新しくPin<P>
のインスタンスを生成します。
P::Target
がUnpin
を実装していない型(ムーブしたら絶対アカン😡型)の場合、
変数のムーブが発生し得るためこのメソッドは利用できません。
Pin::into_inner
impl<P: Deref> Pin<P> where P::Target: Unpin { pub fn into_inner(pin: Pin<P>) -> P; }
型P
がDeref
を、P::Target
がUnpin
を実装している場合にのみ使えるメソッドです。
Pin<P>
に内包している値を取り出し、ピン留めを外します。
P::Target
がUnpin
を実装していない型(ムーブしたら絶対アカン😡型)の場合、
変数のムーブが発生し得るためこのメソッドは利用できません。
Pin::new_unchecked
impl<P: Deref> Pin<P> { pub unsafe fn new_unchecked(pointer: P) -> Pin<P>; }
型P
がDeref
を実装している場合にのみ使える不安全メソッドです。
新しくPin<P>
のインスタンスを生成します。
変数がムーブしても問題ないことに実装者が責任を持つ必要があります。
Pin::as_ref
impl<P: Deref> Pin<P> { pub fn as_ref(&self) -> Pin<&P::Target>; }
型P
がDeref
を実装している場合にのみ使えるメソッドです。
Pin<P>
に内包しているポインタ型の参照先をPin
に包んで返します。
例えばPin<Box<u32>>
はPin<&u32>
に、Pin<&u32>
はPin<&u32>
となります。
後者はPin<&T>
を受け取るメソッドを呼び出したいがPin
の所有権を渡したくないときに使います。
このメソッドは内包する変数をムーブしないため、常に安全です。
Pin::into_inner_unchecked
impl<P: Deref> Pin<P> { pub unsafe fn into_inner_unchecked(pin: Pin<P>) -> P; }
型P
がDeref
を実装している場合にのみ使える不安全メソッドです。
Pin<P>
に内包している値を取り出し、ピン留めを外します。
変数がムーブしても問題が無いことに実装者が責任を持つ必要があります。
Pin::as_mut
impl<P: DerefMut> Pin<P> { pub fn as_mut(&mut self) -> Pin<&mut P::Target>; }
型P
がDerefMut
を実装している場合にのみ使えるメソッドです。
Pin
に内包している値を可変逆参照し、再度Pin
に包んで返すメソッドです。
例えばPin<Box<u32>>
はPin<&mut u32>
となり、Pin<&mut u32>
はPin<&mut u32>
となります。
後者はPin<&mut T>
を受け取るメソッドを呼び出したいがPin
の所有権を渡したくないときに使います。
このメソッドは内包する変数をムーブしないため、常に安全です。
Pin::set
impl<P: DerefMut> Pin<P> { pub fn set(&mut self, value: P::Target) where P::Target: Sized; }
型P
がDerefMut
を実装しており、かつP::Target
のサイズが定まっている場合にのみ使えるメソッドです。
ポインタ型の中身(例えばBox<T>
ではT
)をvalue
で置き換えます。
以前の値はその場で破棄されるため安全です。
Pin::map_unchecked
impl<'a, T: ?Sized> Pin<&'a T> { pub unsafe fn map_unchecked<U, F>(self, func: F) -> Pin<&'a U> where F: FnOnce(&T) -> &U; }
Pin
が参照型を保持している場合にのみ使える不安全メソッドです。
内部の参照型に関数を適用し、その結果をPin
に包んで返します。
主に型T
の保持する値(構造体のフィールドなど)をPin
に包む際に利用します。
ただし、関数の返す参照の用法が正しく、かつムーブしないことに実装者が責任を持つ必要があります。
Pin::get_ref
impl<'a, T: ?Sized> Pin<&'a T> { pub fn get_ref(self) -> &'a T; }
Pin
が参照型を保持している場合にのみ使えるメソッドです。
Pin
が内包する参照型を返します。
多くの場合Deref
を通して透過的に内部の値にアクセスできるため、このメソッドを使う機会はあまりないでしょう。
このメソッドは内包する変数をムーブしないため、常に安全です。
Pin::into_ref
impl<'a, T: ?Sized> Pin<&'a mut T> { pub fn into_ref(self) -> Pin<&'a T>; }
Pin
が可変参照型を保持している場合にのみ使えるメソッドです。
所有権を奪いつつPin
内部の可変参照を参照にして返します。
例えばPin<&mut u32>
はPin<&u32>
となります。
代わりにPin::as_ref
を使うことも検討してみてください。
このメソッドは内包する変数をムーブしないため、常に安全です。
Pin::get_mut
impl<'a, T: ?Sized> Pin<&'a mut T> { pub fn get_mut(self) -> &'a mut T where T: Unpin; }
Pin
が可変参照型を保持しており、かつ型T
がUnpin
を実装している場合にのみ使えるメソッドです。
Pin
が内包する可変参照型を返します。
多くの場合DerefMut
を通して透過的に内部の値にアクセスできるため、このメソッドを使う機会はあまりないでしょう。
ただし、型T
がUnpin
を実装していない型(ムーブしたら絶対アカン😡型)の場合、
可変参照の使い方によっては変数のムーブが発生し得るためこのメソッドは利用できません。
Pin::get_unchecked_mut
impl<'a, T: ?Sized> Pin<&'a mut T> { pub unsafe fn get_unchecked_mut(self) -> &'a mut T; }
Pin
が可変参照を保持している場合にのみ使える不安全メソッドです。
Pin
が内包する可変参照を返します。
このメソッドから返される可変参照から変数がムーブしないことに実装者が責任を持つ必要があります。
Pin::map_unchecked_mut
impl<'a, T: ?Sized> Pin<&'a mut T> { pub unsafe fn map_unchecked_mut<U, F>(self, func: F) -> Pin<&'a mut U> where F: FnOnce(&mut T) -> &mut U; }
Pin
が可変参照を保持している場合にのみ使える不安全メソッドです。
内部の可変参照に関数を適用し、その結果をPin
に包んで返します。
主に型T
の保持する値(構造体のフィールドなど)をPin
に包む際に利用します。
ただし、関数の返す可変参照の用法が正しく、かつムーブしないことに実装者が責任を持つ必要があります。
Pin
をうまく使うためのクレート
pin-utils
変数をスタックでピン留めするためのpin_mut!
マクロ、
self: Pin<&mut Self>
からフィールドを書き換えるためのunsafe_unpinned!
マクロ、
self: Pin<&mut Self>
からフィールドをPin
に包むためのunsafe_pinned!
マクロを提供します。
記事公開時点で、アルファ版である0.1.0-alpha.4がリリースされた2019/7/14以降の音沙汰がありません。 rust-lang-nurseryグループにいますし、せめて正式版をリリースして欲しいのですが・・・。
pin-project
Pin
を安全に写像するための属性マクロなどを提供するクレート。
use std::pin::Pin; use pin_project::{pin_project, pinned_drop, project, project_ref}; #[pin_project(PinnedDrop)] #[derive(Debug)] struct Foo { // Fooを写像した際にxをPinに包む #[pin] x: u32, y: u32, } impl Foo { pub fn x(self: Pin<&mut Self>) -> u32 { // Pin<&mut Self>で受け取ったときはprojectを使って写像する self.project().x() } pub fn y(self: Pin<&Self>) -> u32 { // Pin<&Self>で受け取ったときはproject_refを使って写像する self.project_ref().y() } } // project()で返るオブジェクトにimplする #[project] impl Foo { pub fn x(self) -> u32 { let x: Pin<&mut u32> = self.x; *x } } // project_ref()で返るオブジェクトにimplする #[project_ref] impl Foo { pub fn y(self) -> u32 { let y: &u32 = self.y; *self.y } } // Pin<&mut Self>を受け取るDropを実装する #[pinned_drop] impl PinnedDrop for Foo { fn drop(self: Pin<&mut Self>) { let this = self.project(); println!("Foo {{ x: {}, y: {} }}", this.x, this.y); } }
pin-project-lite
pin-projectから基本機能のみを抜き出したもので、手続きマクロ周りのクレートに依存しないためビルドが早くなる可能性があります。 大抵のプロジェクトでは手続きマクロを使ったクレートに依存しているためその恩恵は無いでしょう。
基本的にはpin-projectを使うと良いと思います。
futures
変数をスタックでピン留めする為のマクロfutures::pin_mut!
を提供します。
このマクロはpin_utils::pin_mut!
マクロを指しており、両者に機能に違いはありません。
tokio
変数をスタックでピン留めする為のマクロtokio::pin!
を提供します。
pin_utils::pin_mut!
マクロとほとんど同じですが、変数を宣言する形で、そのまま変数をPin
に包むことが出来る点が異なります。
tokioを既に使っているなら、pin-utilsを使う代わりにこのマクロを使うと良いでしょう。
fn main() { // pin_utilsもtokioも、宣言済みの変数をスタックでピン留めする機能は同じ let x = 0u32; pin_utils::pin_mut!(x); // xはPin<&mut u32> let y = 0u32; tokio::pin!(y); // yはPin<&mut u32> // 加えて、tokioには変数を宣言する形でそのままPinに包む機能がある tokio::pin! { let z = 0u32; } // zはPin<&mut u32> }
Pinは非同期プログラミングより
そもそも、Pin
は非同期プログラミングを実現するために導入されたものです。
Rust標準では、Unpin
を実装しない、つまり「ムーブしたら絶対アカン😡」オブジェクトは非同期関数の戻り値と非同期ブロックのみです。
async fn func() {} // fもbもUnpinを実装しないオブジェクト let f = func(); let b = async {};
非同期関数などの「途中で中断し、再開できる関数」はCPUでは表現できないため、 内部ではコルーチンとして表現され、その後コンパイラによってステートマシンに変換されます *6。 このステートマシンが自己参照構造体になるのです。
非同期関数をステートマシンとして表現するならばこの様なコードになるでしょう。 ただし、コンパイラによって変換される表現とは大きく異なりますし、動作するコードでもありません。
// 関数としてこう書くと・・・ async fn func() { // State0 let x: u32 = 0; // yはxへの参照 let y: &u32 = &x; // another_funcを呼び出し、待機するためのオブジェクトを得る let future = another_func(); // State1 // another_func()の実際の処理を待機する future.await; // State2 // yは中断前の状態を引き継ぐ println!("{}", y); } // 最終的にはこのようなステートマシンになる enum Func { // 関数実行前の状態 State0, // another_funcを待機している状態 State1 { x: u32, // yはxへの参照。このような構文はない y: &'self u32, // another_funcの戻り値としてのFuture future: AnotherFunc, }, // another_funcを待機したあとの状態 State2 { x: u32, // yはxへの参照。このような構文はない y: &'self u32, } } impl Future for Func { pub fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { unsafe { match self.get_unchecked_mut() { Func::State0 => { // 関数の実行を開始する let x: u32 = 0; let y: &u32 = &x; let future = another_func(); // another_func()の処理を待機するために次のステップに移行する *self = Func::State1 { x, y, future }; Poll::Pending } Func::State1 { ref mut future, .. } => { // another_func()を待機する match Pin::new_unchecked(future).poll(cx) { Poll::Ready(()) => { // another_func()の処理が終われば次のステップに移行する *self = Func::State2 { x, y }; Poll::Pending } Poll::Pending => { // another_func()の処理が終わってなければ次のステップに移行する *self = Func::State1 { x, y, future }; Poll::Pending } } } Func::State2 { x, y } => { println!("{}", y); Poll::Ready(()) } } } } }
非同期関数を実現するためには自己参照構造体が必要であり、そのためにPin
が用意された、ということが分かるでしょう。
さいごに
たかが1つの機能のためにこんなに長い記事を書くことになるとは思いもしませんでした。 Rustはなんて奥が深い言語なんだ・・・。
オプティムでは自己参照しないエンジニアを募集しています。
謝辞
この記事を執筆するにあたり、下記の記事を大変参考にさせていただきました。ありがとうございます。
https://qiita.com/ubnt_intrepid/items/df70da960b21b222d0ad