AxumとMiddleware

AxumはRustのWebアプリフレームワーク。ミドルウェアはtowerが提供するものをそのまま使える。

https://docs.rs/tower-http/latest/tower_http/index.html

静的言語のフレームワークなだけあって、少し癖がある。

std::pin

Future::pollの引数に使われていたりと、何かと登場するstd::pin::Pin。自己参照構造体を作るときに必要だとか、ピン止めされたポインタを表すとは聞くものの、いまいちピンとこない。

色々な文書を読んだが、ここの概要が一番わかりやすかった。まずは読んでみてもいいだろう ⇒ https://doc.rust-lang.org/std/pin/

Pin<P>とは

大前提としてPin<P>はポインタ型である。&TBox<T>Rc<T>のようにderefできるものは広義のポインタといえる。Pin<Box<T>>Box<T>はどちらもderefするとT型に解決されるが、Pin<Box<T>>Box<T>の内部データのムーブを禁止する点で異なる。

rust言語では代入操作をするとインスタンス(and/or 所有権)が移動する。c++のstd::moveが常に適用されるイメージでいい。これは全ての型に共通で、ヒープメモリ上にオブジェクトを保持するBox<T>や, 可変参照&mut Tも例外ではない。代入操作でBox<T>から中身をスタックに取り出したり、可変参照&mut Tがもつインスタンスの可変権限を譲渡できる。当然ながら、中身を取り出したBox<T>やDropされて使用不可になるし、譲渡元の可変参照は使えなくなる。借用と所有権のルールに違反するコードはコンパイルエラーになる。この仕組みがrustの安全性を保証している。

この仕組みのために、Rustでは自己参照構造体を作ることができない。例えば文字列の一部をスライスとして保持する構造体は自己参照構造体になる。c++が分かるなら、this->memberへのポインタ・参照をクラス内に保持するのが危険なことはわかるだろう。この構造体をスタック上に作成すると、構造体をムーブしたときに参照が無効になる。この構造体をヒープ上に作れば安全だと思えるが、RustのBoxの中身はスタックにムーブできるため、絶対の安全は保証できない。いろいろな事情があり、自己参照構造体はどう頑張ってもコンパイルエラーになる。

このような危険な構造体を頑張って定義した場合に、ムーブできないことを保証するデータ型が欲しくなる。それがPin<P>である。Pin<Box<T>>Pin<&mut T>Box<T>&mut Tへのアクセスを隠すことで、ムーブ不可能なポインタ型を提供する。

Pinを使う

Pin<Box<T>>Pin<Arc<T>>のように使う。データはヒープ上に配置し、そのインターフェースをPinに包むことが多い。スタック上に配置されたデータのPinは今のところできない。

次にムーブさせたくないデータ型TPhantomPinnedマーカーを追加し、データ型からUnpin属性を取り除く。TUnpinな構造体の場合、Pin<P>に対してDerefMutが実装される。これは禁止しなければならない。

あとは、Pinにポインタを包んで返せばよい。Box::pinArc::pinなどのメソッドも便利。

例えばBox<T>ではなくPin<Box<T>>を返したいとする。まずデータ型Tを作成し、Boxに格納する。Rustのオブジェクトはスタック上で作られるため、ヒープに移すためにムーブが1回発生する。Box::pinを使ってPin<Box<T>>が保持するヒープ領域にデータを配置したあとに、std::pinのunsafeなapiを使って参照関係を設定する。

(注: Box::newを使っても、オブジェクトはスタックで作られてからヒープに移される。そのため、大きな配列を確保しようとするとスタックメモリ不足で落ちるらしい。そんな仕様だったの...@Rust1.66)

pin_project

pinを実装するヘルパーcrate。

future::pollの第一引数がPin<&mut Self>なので、別のFutureを構造体にFutureを保持してpollを呼びたい場合、本当はPin<&mut Future>に保持しないといけない。これは相当面倒である。

#[pin_project]
struct Struct<T, U> {
    #[pin]
    pinned: T,
    unpinned: U,
}

#[pin_project]指定した型をPin可能な型に設定する。Unpinできなくなり、Pin<Box<Struct>>と指定したときにデータが動かない性質を示す。project()というメソッドが追加され、#[pin]で指定したフィールドをPin<&self T>型に変換してからアクセスできるようになる。

pin: T型をPin<&self T>型に変換する。pin_projectのマクロで、(TBD)

https://users.rust-lang.org/t/how-to-create-large-objects-directly-in-heap/26405 https://stackoverflow.com/questions/53691012/is-there-any-way-to-allocate-a-standard-rust-array-directly-on-the-heap-skippin

ここに、Box<T>&mut Tがインスタンスを安全に保持できないという問題が生じる。Box<T>は内部オブジェクトをムーブできるし、参照&mut Tはやstd::mem::replaceで別のオブジェクトに書き換えられる。

mutの有無は関係なく、通常操作で所有権を奪ったり、借用される場合がある。。可変参照&mut Tはこのように、所有権を奪われれば、参照先を書き換えられてプログラムの安全性が揺らぐ。

そこでPin<P>を使う。Pin<P>はポインタを保持するが、そのポインタを取り出すことを禁止できる。Pin<Box<T>>からBox<T>が、Pin<&ref T>からは&ref Tが取り出せなくなる。

pub fn x() {
    let x: u8 = 1;
    let p: Pin<&u8> = Pin {
        // use of unstable library feature 'unsafe_pin_internals' @rust1.56
        pointer: &x,    
    };
}