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>
はポインタ型である。&T
、Box<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は今のところできない。
次にムーブさせたくないデータ型T
にPhantomPinned
マーカーを追加し、データ型からUnpin
属性を取り除く。T
がUnpin
な構造体の場合、Pin<P>
に対してDerefMut
が実装される。これは禁止しなければならない。
あとは、Pin
にポインタを包んで返せばよい。Box::pin
やArc::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,
};
}