🖐️

Rust でインターフェイスを切るベストプラクティス(1)

2024/01/31に公開

インターフェースは Unsurprising であるべきである

以下 Rust for Rustaceans 3章を要約

ユーザがインターフェースに暗に期待していること

  • iter&self を引数にとる。
  • into_innerself を引数にとる。
  • SomethingErrorstd::error::Error を実装する。
  • Trait は基本的に Debug を実装する。
    • ユーザはオブジェクトを println! できることを期待するため。
  • Trait は基本的に SendSync を実装する。
    • ユーザはオブジェクトを Mutex で囲むことができることを期待するため。
    • できない/しなくて良いならその理由をドキュメントに明記する。
  • Trait は基本的に CloneDefault を実装する。
    • できない/しなくて良いならその理由をドキュメントに明記する。
  • オブジェクトに対して assert_eq! を呼ぶこと期待される場合は PartialEq を実装する。
    • その他 PartialOrd, Hash, Eq, Ord についても適宜実装する。
  • オブジェクトが std::collection のキーとして用いられる場合は PartialOrdHash を実装する。
  • オブジェクトの Serialization/Deserialization が行われることが予想される場合は、 Serde Serialize, Deserialize を実装する。
    • serde という依存を増やしたくない場合、serde を選択制にする。
    • これは Cargo.toml より可能である。
  • 一般的に、ユーザはオブジェクトが Copy であることを期待していない。また Trait の実装者側の視点で言うと、オブジェクトが String などの non-Copy な型をメンバに持ってしまった場合、Copy を実装できなくなるため、後方互換性やメンテナンス性が悪化する。これらの点で Trait に Copy を実装するのは Clone ほど必須ではない。

Trait を参照や Wrapper に対しても実装する

  • Trait を実装した際、&T where T: Trait&mut T where T: Trait, Box<T> where T: Trait に対しても Trait を適宜実装する。
  • IntoIterator&Trait&mut Trait に対して実装することでユーザは気にせず for 文を書ける。
  • Trait が Wrapper 型 (e.g. Arc<T>) である場合 Deref を実装する。そうするとユーザは . を使って内側の型のメソッドを呼ぶことができる。
  • 内側の型の参照を簡単に取り出せることを期待する場合、AsRef を実装する。またユーザが自ら Wrap を外せることを望む場合 From<InnerType>Into<InnerType> を実装する。

柔軟性

  • コードを書くと言うことはコードの Restrictions と Promises を定義することである。
  • Restrictions とはそのコードを使用する際の制約である。
  • Promises とはそのコードが保証する内容である。
  • Trait を定義する際には、Restrictions と Promises を慎重に検討しなければならない。

...続く

Discussion