Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Rustのパターンっぽいやつメモ

パターンとはその言語が抽象化できなかった敗北の歴史である。 しかしどんなに優れた言語であってもあらゆる繰り返しに勝てるわけではない。 人は必ずメタ繰り返しを欲するからだ。 そしてそれはRustも例外ではない。

ここでは、OOPでも知られているパターンよりも、Rustに特有のパターンを思いつく限りまとめてみた。名前は適当。

  • crate splitting
    • でかいcrateを分割して、見通しを良くする・再コンパイルの分量を削減する・並列コンパイルを可能にする
    • 親玉crate(全てにdependする)と殿crate(全てにdependされる)があることが多いので、だいたい束みたいな形になる。
    • num, tokio, rustcなど、でかい所はだいたいこれをやっている
    • rustcのそれは特に見通しは良くない。単に並列コンパイル用っぽい気がする。
    • incrementalが進化してrustcがスレッド並列になったら必要性は薄まるかも
  • test module
    • テスト関数と、テストにしか使わない関数を、 mod tests {} に閉じ込める。
    • 当該モジュールには普通 #[cfg(test)] をつける。
    • 当該モジュール内で use super::*; とすることも多い。テスト対象のモジュールとほぼ同じ見え方になるので。
  • newtype
    • struct NewType(OldType);
    • 型システム上区別される別名をつける。 (型システム上同一の別名をつけるときは type NewType = OldType;)
    • Deref inheritanceと併用することもある。 (OldType のうち特定の条件を満たしたものを NewType にしたときなど)
    • PhantomData が必要なこともある。 struct NewType<X>(OldType, PhantomData<fn() -> X>); とか。
  • panic guard
    • try .. finallybegin .. ensure に相当する処理を、Rustのpanicに対して行う方法。主にunsafeコードで使われる。
    • 「パニックの有無にかかわらず行う処理」をするための専用の構造体を用意し、Dropを実装する。
    • しばしば、この構造体は関数内で定義される。
    • パニックしなかった場合はforgetする、という亜種もある。
  • callback
    • FnOnceクロージャを受け取り、それを必ず呼ぶ。
    • 「入る処理」と「出る処理」がペアであることを強制するために使う。
    • 「出る処理」をDropで書く場合とは異なり、ネストの出る順は入れ替わらないし、forgetにも耐性がある。
    • panic guardと組み合わせれば、panic時も出る処理を強制できる。
  • take pattern
    • 初期化/未初期化を静的に保証できないケースで Option<T> を使う。
    • futures::Futureでは「pollが一度でもReadyを返したら、次はpollは呼ばれない」という規約があるが、これはRustの型ではうまく書けない。 なのでfutures関連コードではかなり使われている。
  • extension trait pattern
    • 任意オブジェクトにメソッドを生やす、Rubyのオープンクラス的なことをしたいときに使う。
    • HogeExt みたいなトレイトを作って、欲しいオブジェクトにだけ実装させる。
    • 使う側は use HogeExt する必要があり、勝手に使われることはない。
    • 命名規則がRFCで定められている、実質公認パターン。
  • prelude pattern
    • ↑のトレイトが多すぎるときに使う。
    • some_crate::preludeというモジュールに、これは入れとけというやつを全部突っ込んでおく。
    • これを使ってると大御所感がある。
  • specialization marker
    • 特殊化のためのマーカートレイト。特定の条件を満たすときに実装しておくと速くなる。
    • 条件を満たさないのに実装するとおかしな挙動になる。
    • マーカーじゃない場合もある。
    • stdのFusedIteratorTrustedLen, TrustedRandomAccessがこれ。
  • specializer trait
    • 特殊化のための内部トレイト。特殊化はトレイト単位でしかできないので、トレイト単位ではない特殊化をするために専用のトレイトを用意する。
    • 公開されているトレイトで特殊化をやるとopen specialization (downstream crateが特殊化を定義できてしまう)になってしまうが、専用のトレイトを用意すればclosed specializationにできるという利点もあるかもしれない。
    • std内の SpecExtend などで使われている。
  • companion struct
    • メソッドと、その戻り値となる構造体が一対一対応している。構造体は、ある特定のトレイトを実装している以外は何のとりえもない。
    • Iteratorなどで使われている。
    • impl Trait が安定化されたので、単純な問題に対してはこれで事足りるようになってきた。
    • しかし、Iteratorのような複雑なケースでは依然としてcompanion struct patternが必要
  • dummy token
    • 宣言マクロでたまに必要になる。
    • こちらの我田引水リンクを参照。
    • 必須じゃなくても、わざわざマクロ名を増やしたくないときに使うことがある。 (mymacro!(expr) が内部で mymacro!(@foo expr) を呼ぶみたいな)
  • Deref inheritance
    • オブジェクト指向が恋しくなったときに使う。
    • struct Sub { base: Super, .. } のような構造体を定義する。
    • SubSuper にDerefするようにする。
    • オーバーライドもダウンキャストもないが、アップキャストがオブジェクト指向っぽく振る舞う。
    • rustcのFnCtxt, TyCtxt, InferCtxtあたりで使われている。
  • struct-enum
  • exhaustive field
    • publicな構造体だけど、今後フィールドが増えそうなときに使う……かもしれない。
    • ダミーのprivate fieldを置けば、外からは今後フィールドが増えそうな感じに見える。
    • ただこれだとupdate syntaxが使えなくなるので、隠しフィールドっぽい名前のpublic fieldを置いて #[doc(hidden)] をつけるほうが良いかも。
  • exhaustive variant
    • enumのvariantを今後も増やしたい、というときに使う。
    • やりかたは上と同じで、ダミーのvariantを用意して #[doc(hidden)] をつける。
    • ErrorKind で使われている。
  • {expr}
    • (expr){expr} は挙動が異なる場合がある。
    • 特に、再借用を抑制する効果があり、&mut Tから&mut Tを取り出して再代入するような処理をするときに使われる。(単方向連結リストなど)
    • 詳細はbluss氏のブログを参照。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.