RustのPinチョットワカル

こんにちは。 先日、しばらく不動の一位を守ってきたRustをVSCodeで使う記事を抜き、 私の書いた非同期プログラミングの記事の記事が一番人気になったと思いきや数日でまた抜き返されて傷心中の、 R&Dチームの齋藤(@aznhe21)です。

さて、Rustの非同期プログラミングで時々Pinを使ったり、コンパイラにUnpinが不足していると怒られたりしませんか? そんな時によく分からずuseしたり別の手段を取ったりしていませんか? 今回、このままではマズいと思ってPinを勉強して完全に理解しましたので、その成果を皆さんと共有したいと思います。

更新履歴

  • 03/10
    • 指摘を受け下記2点を修正しました

対象読者

この記事は下記全てに当てはまる人を想定して執筆しています。

  • Rustのトレイトを使える
  • std::pin::Pinを詳しく知りたい
  • 変数がムーブしたとき、その変数のアドレスが変わることを理解している
  • スタックやヒープが何であるかを知っている

つまり・・・どういうことだってばよ(TL;DR)

  • Pin型の変数は、それ自体をムーブしても内部に保持するポインタのアドレス\は変わらない
  • この性質を利用すれば自己参照構造体を安全に取り扱うことが出来る
  • 値の変更は内部フィールドのアドレスが変わる可能性があるため、Pin<P>から可変参照を得るためにはPが、 「変更しても安全」であることを示すUnpinトレイトを満たす必要がある

スPinスPinスPin

Pinを使うその前に

まずはよく一緒に使われる非同期プログラミングとは切り離し、PinUnpinがどういった部分で必要になるかを説明しましょう。

なぜ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);
}

このとき、SelfRefptrは常にxへのアドレスを保持していて欲しいわけですが、 コンストラクタから値を返した時点で変数のアドレスが変わってしまうためこのコードはうまく動きません。

このような「ムーブしたら絶対アカン😡型」をうまく使うために、PinUnpinという仕組みが用意されています。

ムーブしてもおけまる🙆な型のための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,
}

PhantomPinnedUnpin実装しない型です。 そして、自動トレイトの機能によって「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::replacestd::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の中身やヒープに確保したxyにムーブされてしまいます。 これは由々しき事態です。

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にはDerefDerefMut、あるいは&T&mut Tが来ることが分かります。 DerefMutDerefを要求し、また&T&mut TはそれぞれDerefDerefMutを満たすため、 実質的に PinDerefを要求しているのです

PinDerefを要求するということは、Pinは逆参照の出来る型、つまり&TBox<T>などを要求します。 そして、これらの型をPinで包むと、Unpinを実装しない「ムーブしたら絶対アカン😡型」でのDerefMutの使用を制限します。 DerefMutが使えないということはPinを通して変数を可変参照として扱えないということです。 変数を可変参照で扱えると、以前の章で説明した通りstd::mem::replaceなどによって変数の中身を取り出すことが出来ます。 逆に言えば可変参照として扱えないのなら変数のムーブを防ぐことが出来るのです。 PinDerefMutの使用を制限しますから、翻って DerefMutの使えないPinは変数のムーブを防ぐことが出来るというわけです。

ここでPinDerefDerefMutの実装を見てみましょう*2。 これを見るとPinDerefMutは「ムーブしてもおけまる🙆な型」(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>::newPDerefであり、かつDeref::TargetUnpinを実装する場合のみ使うことができるもので、 逆に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)への糖衣構文なのです。この構文と見比べれば違和感は薄れると思います。

selfPinとして受け取った場合、それはピン留めされた参照であるため、 その中身は構造体の外ではムーブされないことになります。 Pin<&mut Self>は自身を変更できるピン留めされた参照ですし、 他にもPin<&Self>という構文をピン留めされた不変参照として使うことも出来ます。 なお、不変参照であればDerefを通して&selfを受け取るメソッドをそのまま呼び出すことも出来ますが、 こちらはどこかでムーブされるかもしれないメソッドであるため、&selfPin<&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>;
}

PDerefを、P::TargetUnpinを実装している場合にのみ使えるメソッドです。

新しくPin<P>のインスタンスを生成します。

P::TargetUnpinを実装していない型(ムーブしたら絶対アカン😡型)の場合、 変数のムーブが発生し得るためこのメソッドは利用できません。

Pin::into_inner

impl<P: Deref> Pin<P> where P::Target: Unpin {
    pub fn into_inner(pin: Pin<P>) -> P;
}

PDerefを、P::TargetUnpinを実装している場合にのみ使えるメソッドです。

Pin<P>に内包している値を取り出し、ピン留めを外します。

P::TargetUnpinを実装していない型(ムーブしたら絶対アカン😡型)の場合、 変数のムーブが発生し得るためこのメソッドは利用できません。

Pin::new_unchecked

impl<P: Deref> Pin<P> {
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P>;
}

PDerefを実装している場合にのみ使える不安全メソッドです。

新しくPin<P>のインスタンスを生成します。

変数がムーブしても問題ないことに実装者が責任を持つ必要があります。

Pin::as_ref

impl<P: Deref> Pin<P> {
    pub fn as_ref(&self) -> Pin<&P::Target>;
}

PDerefを実装している場合にのみ使えるメソッドです。

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;
}

PDerefを実装している場合にのみ使える不安全メソッドです。

Pin<P>に内包している値を取り出し、ピン留めを外します。

変数がムーブしても問題が無いことに実装者が責任を持つ必要があります。

Pin::as_mut

impl<P: DerefMut> Pin<P> {
    pub fn as_mut(&mut self) -> Pin<&mut P::Target>;
}

PDerefMutを実装している場合にのみ使えるメソッドです。

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;
}

PDerefMutを実装しており、かつ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が可変参照型を保持しており、かつ型TUnpinを実装している場合にのみ使えるメソッドです。

Pinが内包する可変参照型を返します。 多くの場合DerefMutを通して透過的に内部の値にアクセスできるため、このメソッドを使う機会はあまりないでしょう。

ただし、型TUnpinを実装していない型(ムーブしたら絶対アカン😡型)の場合、 可変参照の使い方によっては変数のムーブが発生し得るためこのメソッドは利用できません。

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

ライセンス表記

  • 冒頭の画像中にはRust公式サイトで配布されているロゴを使用しており、 このロゴはMozillaによってCC-BYの下で配布されています
  • 冒頭の画像はいらすとやさんの画像を使っています。いつもありがとうございます

*1:現在はNightly限定の機能です

*2:Nightly限定の機能を避けるために少し修正しています

*3:Nightly限定の機能を避けるために少し修正しています

*4:tokio v0.2.13からの機能です

*5:Option<NonNull<T>>を使うと良いでしょう

*6:現在のRustにはコルーチンはありませんが、NightlyではGeneratorトレイトと共に実装されています