Rust APIガイドライン (非公式日本語訳)
これはプログラミング言語RustのためのAPIデザイン上の推奨事項をまとめたものです。 大部分は、Rustエコシステム内の標準・非標準ライブラリを構築してきた経験を元に、 Rustのライブラリーチームによって執筆されました。
これはあくまで指針です。 項目ごとの充実度には差があり、今後の加筆修正が必要となっている曖昧な部分もあります。 Rustのクレート開発者は、ここから使えそうな内容を選び取り、 イディオマティックで相互運用性の高いライブラリを開発する上での重要な検討事項とするべきです。 また、このガイドラインは義務的に従わなければならない基準とされるべきではなく、 あくまで従わない場合に比べエコシステム内の他のクレートとのより良い相互運用が達成できるような 物となっています。
このブックはチェックリストと各項目の説明に分かれています。 チェックリストは簡潔に全項目をまとめたものです。 クレートのレビューを行う際に、ざっと目を通すのに使えます。 残りそれぞれの項目に関して章分けし、詳しい説明を行っています。
APIガイドラインへの貢献に興味がある場合、contributing.mdを参照し、 Gitterチャンネルに参加してください。
Rust API Guidelines Checklist
- 命名 (クレートがRustの慣用的な命名規則に従っている)
- 相互運用性 (クレートが他のクレートの機能とうまく連携できる)
- 積極的に一般的なトレイトを型に実装している (C-COMMON-TRAITS)
Copy
,Clone
,Eq
,PartialEq
,Ord
,PartialOrd
,Hash
,Debug
,Display
,Default
- 変換に標準のトレイト
From
,AsRef
,AsMut
を用いている (C-CONV-TRAITS) - コレクションが
FromIterator
とExtend
を実装している (C-COLLECT) - データ構造がSerdeの
Serialize
とDeserialize
を実装している (C-SERDE) - 型が可能な限り
Send
,Sync
である (C-SEND-SYNC) - エラー型の意味が分かりやすく、行儀の良い実装となっている (C-GOOD-ERR)
- バイナリ数値型が
Hex
,Octal
,Binary
によるフォーマットをサポートしている (C-NUM-FMT) - 読み書きを行うジェネリックな関数が
R: Read
とW: Write
を値渡しで受け取っている (C-RW-VALUE)
- 積極的に一般的なトレイトを型に実装している (C-COMMON-TRAITS)
- マクロ (クレートが行儀のよいマクロを提供している)
- 入力の構文から結果をイメージできるようになっている (C-EVOCATIVE)
- アイテムを宣言するマクロが属性と衝突しない (C-MACRO-ATTR)
- アイテムを宣言するマクロがアイテムを宣言できる場所のどこでも使える (C-ANYWHERE)
- アイテムを宣言するマクロが可視性の指定をサポートしている (C-MACRO-VIS)
- 型の指定が柔軟である (C-MACRO-TY)
- ドキュメンテーション (クレートに十分なドキュメントが付けられている)
- クレートレベルにコード例付きの詳細なドキュメントがある (C-CRATE-DOC)
- 全てのアイテムにコード例が付いている (C-EXAMPLE)
- コード例が
try!
やunwrap
ではなく?
を使っている (C-QUESTION-MARK) - 関数のドキュメントにエラー、パニック、安全性に関する事項が含まれている (C-FAILURE)
- 文章に関係する項目へのリンクを含める (C-LINK)
- Cargo.tomlが一般的なメタデータを全て含んでいる (C-METADATA)
- authors, description, license, homepage, documentation, repository, readme, keywords, categories
- html_root_url属性が"https://docs.rs/CRATE/X.Y.Z"に設定されている (C-HTML-ROOT)
- 大きな変更が全てリリースノートに記載されている (C-RELNOTES)
- 無用な実装詳細がRustdocに表示されていない (C-HIDDEN)
- 予測性 (クレートを使い、見かけ通りに動作する読みやすいコードが書ける)
- スマートポインタがinherentメソッドを持っていない (C-SMART-PTR)
- 変換メソッドが最も関係の深い型に付いている (C-CONV-SPECIFIC)
- 明確なレシーバを持つ関数がメソッドになっている (C-METHOD)
- 関数がoutパラメータを持たない (C-NO-OUT)
- 奇妙な演算子オーバーロードを行っていない (C-OVERLOAD)
-
Deref
とDerefMut
を実装しているのはスマートポインタだけである (C-DEREF) - コンストラクタはスタティックなinherentメソッドである (C-CTOR)
- 柔軟性 (クレートが実用的なユースケースを幅広くカバーしている)
- 重複した処理を行わなくて済むように中間生成物を公開している (C-INTERMEDIATE)
- 呼び出し側がデータをコピーするタイミングを決める (C-CALLER-CONTROL)
- ジェネリクスを用いて関数の引数に対する制限を最小にしている (C-GENERIC)
- トレイトオブジェクトとして有用なトレイトはオブジェクトセーフになっている (C-OBJECT)
- 型安全性 (クレートが型システムを有効に活用している)
- newtypeを使って静的に値を区別する (C-NEWTYPE)
-
bool
やOption
の代わりに意味のある型を使っている (C-CUSTOM-TYPE) - フラグの集合を列挙型ではなく
bitflags
で表している (C-BITFLAG) - 複雑な値の生成にビルダーパターンを使っている (C-BUILDER)
- 信頼性 (クレートが間違ったことをしない)
- 関数が引数を検証している (C-VALIDATE)
- デストラクタが失敗しない (C-DTOR-FAIL)
- ブロックする可能性のあるデストラクタには代替手段を用意する (C-DTOR-BLOCK)
- デバッガビリティ (クレートが容易なデバッグを支援している)
- 全てのパブリックな型に
Debug
を実装する (C-DEBUG) -
Debug
表現を空にしない (C-DEBUG-NONEMPTY)
- 全てのパブリックな型に
- 将来性 (クレートを、ユーザのコードを壊すことなく改善できる)
- sealedトレイトを使って下流の実装を適切に防いでいる (C-SEALED)
- 構造体のフィールドを適切にプライベートにする (C-STRUCT-PRIVATE)
- newtypeを用いて実装詳細を隠蔽している (C-NEWTYPE-HIDE)
- データ構造にderiveしたトレイトの境界を定義で繰り返さない (C-STRUCT-BOUNDS)
- 必要事項 (ある状況下では重要な問題)
- stableなクレートのパブリックな依存クレートがstableである (C-STABLE)
- クレートとその依存先がpermissiveなライセンスの下にある (C-PERMISSIVE)
命名
大文字・小文字の使い分けがRFC430に従っている (C-CASE)
Rustにおける基本的な命名規則はRFC 430に記述されています。
Rustは「型レベル」のもの(型やトレイト)にCamelCase
、
「値レベル」のものにsnake_case
を使用する傾向があります。
正確には、次表のように命名します。
アイテム | 規則 |
---|---|
クレート | 不明 |
モジュール | snake_case |
型 | CamelCase |
トレイト | CamelCase |
Enumのバリアント | CamelCase |
関数 | snake_case |
メソッド | snake_case |
一般のコンストラクタ | new または with_more_details |
変換を行うコンストラクタ | from_some_other_type |
マクロ | snake_case! |
ローカル変数 | snake_case |
スタティック変数 | SCREAMING_SNAKE_CASE |
定数 | SCREAMING_SNAKE_CASE |
型パラメータ | 簡潔なCamelCase 、大抵は大文字で1文字: T |
ライフタイム | 短いlowercase 、大抵は1文字: 'a , 'de , 'src |
Feature | 不明、ただしC-FEATUREを参照 |
頭字語や複合語は、CamelCase
では一語に数えます。例えばUUID
ではなくUuid
を使い、USize
ではなくUsize
、StdIn
ではなくStdin
を使って下さい。
snake_case
ではis_xid_start
のように小文字にします。
snake_case
またはSCREAMING_SNAKE_CASE
では、
それが最後の文字を除いて一文字で区切ってはいけません。
例えば、b_tree_map
ではなくbtree_map
としますが、PI2
ではなくPI_2
とします。
クレート名の前後に-rs
や-rust
を付けるべきではありません。
全てのクレートがRust製であることは分かりきっています!
そのことをユーザに常に知らせ続ける必要はありません。
(訳注: レポジトリ名等ではなく、Cargo.tomlで指定するクレート名についての指針です)
標準ライブラリでの例
この規則は標準ライブラリの全体に渡って使用されています。
変換メソッドにas_
, to_
, into_
を使っている (C-CONV)
変換メソッドの名前には次のプレフィクスを付けるべきです。
プレフィクス | コスト | 所有権 |
---|---|---|
as_ | 低い | 借用 -> 借用 |
to_ | 高い | 借用 -> 借用 借用 -> 所有 (Copyでない型) 所有 -> 所有 (Copy型) |
into_ | 可変 | 所有 -> 所有 (Copyでない型) |
以下に例を挙げます。
str::as_bytes()
はコストなしに、str
をUTF-8バイト列として見たビューを返します。 入力は借用された&str
で出力は借用された&[u8]
です。Path::to_str
はOSの与えるパスのバイト列に対し、コストの高いUTF-8チェックを行います。 入出力はともに借用されています。小さくない実行時コストがあるため、これをas_str
と呼ぶことはできません。str::to_lowercase()
はUnicode規格に沿ってstr
を小文字に変換したものを返します。 この処理は文字列のデコードを含み、またメモリの確保も行うでしょう。 入力は借用された&str
で出力は所有されたString
です。f64::to_radians()
は浮動小数点数で表された角度を度からラジアンに変換します。 入力はf64
です。これが&f64
でないのは、コピーに殆どコストが掛からないためです。 入力が消費されないので、このメソッドをinto_radians
と呼ぶのはミスリーディングです。String::into_bytes()
はString
がラップしているVec<u8>
を取り出します。 この処理にコストは掛かりません。このメソッドはString
の所有権を得て、所有されたVec<u8>
を返します。BufReader::into_inner()
はバッファリングされたreaderの所有権を得て、ラップされていたreaderを取り出します。 この処理にコストは掛かりません。バッファ内のデータは破棄されます。BufWriter::into_inner()
はバッファリングされたwriterの所有権を得て、ラップされていたwriterを取り出します。 この処理にはコストの掛かるバッファのフラッシングが必要なことがあります。
as_
やinto_
の付くような変換メソッドは一般に抽象を弱めます。
内部表現を公開したり(as
)、データを内部表現に分解したり(into
)することになるからです。
一方で、to_
と付くような変換メソッドは一般に抽象レベルを保てることが多いですが、
内部で何らかの処理を行いデータの表現方法を変換する必要があります。
ある値をラップして高レベルの意味を持たせるような型において、
ラップされた型にアクセスさせる手段はinto_inner()
メソッドによって提供されるべきです。
これは例えば、バッファリングを提供するBufReader
や、
エンコード・デコードを行うGzDecoder
、
アトミックなアクセスを提供するAtomicBool
といった型のようなセマンティクスを持つ型に適用できます。
変換メソッドの名前にmut
を含めるときは、返り値の型の記述と同じ順番にしてください。
例えばVec::as_mut_slice
はミュータブルは名前の通りスライスを返します。
例えばas_slice_mut
等よりもこのような命名が推奨されます。
# #![allow(unused_variables)] #fn main() { // Return type is a mut slice. fn as_mut_slice(&mut self) -> &mut [T]; #}
標準ライブラリでのさらなる例
Getterの名前がRustの規則に従っている (C-GETTER)
後述するような例外を除き、getterの名前をget_
で始めるべきではありません。
# #![allow(unused_variables)] #fn main() { pub struct S { first: First, second: Second, } impl S { // Not get_first. pub fn first(&self) -> &First { &self.first } // Not get_first_mut, get_mut_first, or mut_first. pub fn first_mut(&mut self) -> &mut First { &mut self.first } } #}
get
という命名は、getterによって得られるものが自明である場合にのみ使用されるべきです。
例えば、Cell::get
はCell
の中身を返します。
境界検査といった、実行時のバリデーションが必要なgetterには、
unsafeな_unchecked
版も用意できないか検討してください。
そのようなメソッドの宣言はふつう、以下のようになります。
# #![allow(unused_variables)] #fn main() { fn get(&self, index: K) -> Option<&V>; fn get_mut(&mut self, index: K) -> Option<&mut V>; unsafe fn get_unchecked(&self, index: K) -> &V; unsafe fn get_unchecked_mut(&mut self, index: K) -> &mut V; #}
getterと変換(C-CONV)の間の違いは往々にして微かなもので、
常に境界がハッキリしている訳ではありません。
例えばTempDir::path
は一時ディレクトリのファイルシステム上のパスのgetterとして捉えられますが、
TempDir::into_path
は一時ディレクトリを削除する責任を呼び出し側に移す変換メソッドです。
このような場合path
はgetterなので、get_path
やas_path
と呼ぶことは正しくありません。
標準ライブラリでのさらなる例
std::io::Cursor::get_mut
std::ptr::Unique::get_mut
std::sync::PoisonError::get_mut
std::sync::atomic::AtomicBool::get_mut
std::collections::hash_map::OccupiedEntry::get_mut
<[T]>::get_unchecked
イテレータを生成するメソッドの名前がiter
, iter_mut
, into_iter
となっている ([C-ITER])
RFC 199に従ってください。
U
という型の要素を持つコンテナの場合、イテレータを生成するメソッドは次のように命名するべきです。
# #![allow(unused_variables)] #fn main() { fn iter(&self) -> Iter // Iter implements Iterator<Item = &U> fn iter_mut(&mut self) -> IterMut // IterMut implements Iterator<Item = &mut U> fn into_iter(self) -> IntoIter // IntoIter implements Iterator<Item = U> #}
この指針は全ての要素が同質であると意味付けられたコレクションに対して適用されます。
例えばstr
はバイト列ですが、有効なUTF-8であるという保証があるため、この指針は適用できません。
従ってiter
/iter_mut
/into_iter
といったメソッド群ではなく、
バイト列としてイテレートするstr::bytes
、キャラクタ列としてイテレートするstr::chars
を持ちます。
このガイドラインはメソッドにのみ適用され、関数は対象外です。
例えばurl
クレートのpercent_encode
は文字列をパーセントエンコーディングしていくイテレータを返します。
この場合、iter
/iter_mut
/into_iter
といった命名を使うことに利点はありません。
標準ライブラリでの例
イテレータの型名が、それを生成するメソッドと揃っている (C-ITER-TY)
into_iter
というメソッドはIntoIter
という型を返すべきです。
他のイテレータを返すメソッドでも同様です。
この指針は主にメソッドに対して適用されますが、関数に対しても大抵は適用できます。
例えばurl
クレートのpercent_encode
関数はPercentEncode
という型名のイテレータを返します。
このような命名法はvec::IntoIter
のようにモジュール名を付けて呼ぶ際に最も有用です。
標準ライブラリでの例
Vec::iter
はIter
を返す。Vec::iter_mut
はIterMut
を返す。Vec::into_iter
はIntoIter
を返す。BTreeMap::keys
はKeys
を返す。BTreeMap::values
はValues
を返す。
Featureの名前に余計な単語が入っていない (C-FEATURE)
Cargoのfeatureの名前に意味のない単語を付けないでください。
use-abc
やwith-abc
などとせず、単にabc
とするべきです。
標準ライブラリへの依存がオプションである場合が最もよく目にする例でしょう。 これを指針に従って行うと、以下のようになります。
# In Cargo.toml
[features]
default = ["std"]
std = []
# #![allow(unused_variables)] #fn main() { // In lib.rs #![cfg_attr(not(feature = "std"), no_std)] #}
このfeatureにstd
以外の、use-std
やwith-std
あるいはその他の独創的な名前を付けないでください。
そうすることで、Cargoが暗黙的に追加するオプショナルな依存性のfeatureと沿った形になります。
例えばx
というクレートがSerdeと標準ライブラリに対する依存をオプションとして持つとき、
[package]
name = "x"
version = "0.1.0"
[features]
std = ["serde/std"]
[dependencies]
serde = { version = "1.0", optional = true }
のようになります。そして、さらにx
に依存するとき、Serdeへの依存を
features = ["serde"]
で有効化できます。
また、同じように標準ライブラリへの依存をfeatures = ["std"]
で有効化できます。
Cargoによって暗黙的に追加されるfeatureはserde
であり、use-serde
でもwith-serde
でもありません。
ですから、明示的なfeatureに対しても同じようにするべきなのです。
関連事項として、Cargoのfeatureは追加式ですから、
no-abc
といったfeatureは大きな間違いです。
命名時に単語を並べる順番が揃っている (C-WORD-ORDER)
標準ライブラリにおけるエラー型をいくつか示します。
JoinPathsError
ParseBoolError
ParseCharError
ParseFloatError
ParseIntError
RecvTimeoutError
StripPrefixError
全て、動詞-オブジェクト-エラーの順番で並んでいます。
もし新たにアドレスのパースに失敗したことを表すエラーを追加するならば、
AddrParseError
等ではなく、
一貫性を考えて動詞-オブジェクト-エラーの順に並べParseAddrError
とすべきです。
どの順番を選ぶかは大して重要ではありませんが、クレート内での一貫性、 標準ライブラリの似た機能との整合性には注意してください。
相互運用性
積極的に一般的なトレイトを型に実装している (C-COMMON-TRAITS)
Rustのトレイトシステムは 孤立 を許容しません。
大まかに言うと、全てのimpl
はトレイトのあるクレートか、実装対象の型があるクレートに置かれる必要があります。
従って、型を定義するクレートは積極的に、可能な限り全ての一般的なトレイトを実装すべきです。
次のような状況を考えてみてください。
std
クレートがDisplay
トレイトを宣言。url
クレートがUrl
型を宣言、Display
を実装せず。webapp
クレートがstd
とurl
をインポート。
この場合、url
がDisplay
を実装していない以上、webapp
にはどうすることもできません。
(newtypeパターンは有効な回避策ですが、不便です。)
最も重要な、一般的なトレイトをstd
から挙げます。
ある型にDefault
を実装し、引数を取らないnew
コンストラクタを追加するのは
一般的かつ開発者の予期するところであることに注意してください。
new
は慣用的なコンストラクタであり、利用者はそれが存在することを期待します。
従ってコンストラクタが引数を取らないのが自然であれば、
default
と機能的に同一であったとしてもコンストラクタが存在しているべきです。
変換に標準のトレイトFrom
, AsRef
, AsMut
を用いている (C-CONV-TRAITS)
以下の変換用トレイトは理にかなう限り実装されているべきです。
以下の変換用トレイトは実装されるべきではありません。
上記のトレイトはFrom
, TryFrom
を基にしたブランケット実装を持つので、
こちらを代わりに実装してください。
標準ライブラリでの例
From<u16>
がu32
に実装されています。幅の小さい整数は常に幅の大きい整数に変換可能であるためです。From<u32>
はu16
に実装されていません。数値が大きすぎると変換できないためです。TryFrom<u32>
がu16
に実装されており、u16
に収まらないほど数値が大きければエラーを返します。From<Ipv6Addr>
がIpAddr
に実装されています。この型はv4とv6の両方のIPアドレスを表すことができます。
コレクションがFromIterator
とExtend
を実装している (C-COLLECT)
FromIterator
とExtend
を実装すると、
そのコレクションは以下のイテレータメソッドと共に使うことができるようになります。
FromIterator
はイテレータが含んでいる値からコレクションを生成するものです。
Extend
は既存のコレクションにイテレータの含む値を追加します。
標準ライブラリでの例
Vec<T>
はFromIterator<T>
とExtend<T>
の双方を実装しています。
データ構造がSerdeのSerialize
とDeserialize
を実装している (C-SERDE)
データ構造として使われる型はSerialize
及びDeserialize
を実装しているべきです。
明らかにデータ構造である型とそうでない型の間にはグレーゾーンが存在します。
LinkedHashMap
やIpAddr
はデータ構造です。
LinkedHashMap
やIpAddr
をJSONファイルから読み取りたい、
あるいはIPCを用いて他のプロセスに送りたいというのは当然の要求でしょう。
一方でLittleEndian
はデータ構造ではりません。
これはbyteorder
の使うマーカ型で、 最適化によって消えるため実行時には存在しません。
これらは違いが明らかな例です。曖昧な例に遭遇した場合、必要ならば
IRCの#rustあるいは#serdeチャンネルで助言を求めることができます。
クレートが他の用途でSerdeに依存していなければ、 Cargoのfeatureを使ってSerdeへの依存をオプショナルにしたいと思われるかもしれません。 そうすることで、下流のライブラリがSerdeへの依存を必要とする場合のみSerdeがコンパイルされるようになります。
他のSerdeベースのライブラリとの一貫性を保つため、Cargoのfeatureの名前は単に"serde"
とすべきです。
その他、"serde_impls"
や"serde_serialization"
のような名前は使わないでください。
deriveを使わない場合、典型的な例は以下のようになります。
[dependencies]
serde = { version = "1.0", optional = true }
# #![allow(unused_variables)] #fn main() { #[cfg(feature = "serde")] extern crate serde; struct T { /* ... */ } #[cfg(feature = "serde")] impl Serialize for T { /* ... */ } #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for T { /* ... */ } #}
そして、deriveを使う場合は以下のようになります。
[dependencies]
serde = { version = "1.0", optional = true, features = ["derive"] }
# #![allow(unused_variables)] #fn main() { #[cfg(feature = "serde")] #[macro_use] extern crate serde; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct T { /* ... */ } #}
型が可能な限りSend
,Sync
である (C-SEND-SYNC)
Send
およびSync
はコンパイラが可能だと判断できれば自動的に実装されます。
生ポインタを操作する型の場合、Send
およびSync
の実装が
その型のスレッドセーフ特性を正確に表すように注意してください。
以下のようなテストを追加することで、
Send
やSync
の実装が意図せず外れるような退行バグの発生を防ぐことができます。
# #![allow(unused_variables)] #fn main() { #[test] fn test_send() { fn assert_send<T: Send>() {} assert_send::<MyStrangeType>(); } #[test] fn test_sync() { fn assert_sync<T: Sync>() {} assert_sync::<MyStrangeType>(); } #}
エラー型の意味が分かりやすく、行儀の良い実装となっている (C-GOOD-ERR)
エラー型とは、パブリックな関数から返されるResult<T, E>
における、任意のE
のことです。
エラー型は常にstd::error::Error
トレイトを実装しているべきです。
これは例えばerror-chain
のようなエラー処理ライブラリによって異なるエラー型を抽象化して扱うために使われており、
その型を別の型のエラーのcause()
として使うことができるようになります。
加えて、エラー型はSend
、Sync
トレイトを実装するべきです。
Send
でないエラー型はthread::spawn
によって作られたスレッドから返すことはできません。
そしてSync
でない型はArc
を用いて複数のスレッドから参照することかできません。
これらはマルチスレッディングアプリケーションにおける、基本的なエラー処理での一般的な要求です。
Send
とSync
は、Send + Sync + Error
を要求するstd::io::Error::new
を用いて
カスタムエラーをIOエラーに変換する際にも必要です。
この指針について、注意が必要なのはreqwest::Error::get_ref
のようなErrorトレイトオブジェクトを返す関数です。
普通はError + Send + Sync + 'static
が呼び出し側にとって最も有用です。
'static
を追加することにより、Error::downcast_ref
を使うことが可能になります。
例えエラーとして返せる有用な情報が無かったとしても、決して()
をエラー型として使わないでください。
()
はError
を実装していないため、error-chain
のようなエラー処理ライブラリと共に使うことができません。()
はDisplay
を実装していないため、利用者が自分でエラー時のメッセージを記述する必要があります。()
はDebug
を実装していますが、unwrap()
した際に役に立たないものです。- 下流のクレートが
?
演算子を使うためには、意味の不明瞭なFrom<()>
をエラー型に実装しければなりません。
代わりに、クレートや関数毎に意味の分かるエラー型を定義し、
適切なError
およびDisplay
の実装を追加してください。
返すべき情報がないならばユニット構造体型として定義することができます。
# #![allow(unused_variables)] #fn main() { use std::error::Error; use std::fmt::Display; // これの代わりに…… fn do_the_thing() -> Result<Wow, ()> // こうして下さい fn do_the_thing() -> Result<Wow, DoError> #[derive(Debug)] struct DoError; impl Display for DoError { /* ... */ } impl Error for DoError { /* ... */ } #}
Display
によって与えられるエラーメッセージは小文字とし、最後に約物は付けないで下さい。
そして普通は簡潔なものにしてください。
Error::description()
の返すメッセージはあまり気にする必要はありません。
description()
ではなく常にDisplay
を使ってエラーを表示すべきですから、
適当に"JSON error"
のようなもので十分です。
標準ライブラリでの例
ParseBoolError
は、文字列をboolとしてパースするのに失敗した際、返される型です。
エラーメッセージの例
- "unexpected end of file"
- "provided string was not `true` or `false`"
- "invalid IP address syntax"
- "second time provided was later than self"
- "invalid UTF-8 sequence of {} bytes from index {}"
- "environment variable was not valid unicode: {:?}"
バイナリ数値型がHex
, Octal
, Binary
によるフォーマットをサポートしている (C-NUM-FMT)
これらのトレイトはフォーマット指定子{:X}
、{:x}
、{:o}
、および{:b}
を使用した際の
表現を制御します。
|
や&
などビット単位での操作が行われる数値型にはこれらのトレイトを実装してください。
これは特にビットフラグ型において妥当です。
struct Nanoseconds(u64)
のような、量を表す数値型においては必要ないでしょう。
読み書きを行うジェネリックな関数がR: Read
とW: Write
を値渡しで受け取っている (C-RW-VALUE)
標準ライブラリは次の2つの実装を含んでいます。
# #![allow(unused_variables)] #fn main() { impl<'a, R: Read + ?Sized> Read for &'a mut R { /* ... */ } impl<'a, W: Write + ?Sized> Write for &'a mut W { /* ... */ } #}
従って、R: Read
あるいはW: Write
という境界を持つジェネリックなパラメータを値で受け取る関数は、
必要ならばミュータブルな参照を受け取ることも可能になっています。
そういった関数のドキュメントには、ミュータブルな参照を渡すこともできると簡単に書いてあるべきです。
新規のRustユーザが頻繁にこの点に引っ掛かるためです。
例えば、複数回に渡ってあるファイルからデータを読み出したいにも関わらず、
関数がreaderを値で受け取るという場合、対処方法の分からないユーザがいます。
この場合、上記の実装を利用してf
の代わりに&mut f
を代わりに渡せるというのが答えです。
例
flate2::read::GzDecoder::new
flate2::write::GzEncoder::new
serde_json::from_reader
serde_json::to_writer
マクロ
入力の構文から結果をイメージできるようになっている (C-EVOCATIVE)
Rustのマクロの入力ではお好きな構文を実装することができますが、 特異なシンタックスを導入するのではなく、 Rustの構文に似せることでマクロ以外のコードとの統一性を保てるようにしてください。 キーワードや記号類の選択・配置には注意してください。
良い指針は、マクロの出力に似せた構文(特にキーワードと記号類)を使用することです。
例えば、マクロが与えられた名前の構造体型を宣言するならば、
マクロの入力において名前の前にキーワードstruct
を付けさせるようにし、
コードを読む人に対して構造体型が宣言されるということを知らせてください。
# #![allow(unused_variables)] #fn main() { // このようにしてください... bitflags! { struct S: u32 { /* ... */ } } // ...キーワードがなかったり... bitflags! { S: u32 { /* ... */ } } // ...その場その場で勝手な単語を導入したりしないでください bitflags! { flags S: u32 { /* ... */ } } #}
もう一つの例はセミコロンとコンマの問題です。 Rustにおける定数の宣言では、最後にセミコロンを付けます。 複数の定数を宣言するマクロの場合、Rustの構文と違っているとしても同様にセミコロンを付けるようにすべきでしょう。
# #![allow(unused_variables)] #fn main() { // 普通の定数の宣言にはセミコロンが使われます const A: u32 = 0b000001; const B: u32 = 0b000010; // なので、こうすべきです... bitflags! { struct S: u32 { const C = 0b000100; const D = 0b001000; } } // ...こうではなく bitflags! { struct S: u32 { const E = 0b010000, const F = 0b100000, } } #}
マクロには幅広い用例があるため、こういった特定の例はそのまま通用しないでしょう。 ですが、同じような考え方を適用できないか考えてみて下さい。
アイテムを宣言するマクロが属性と衝突しない (C-MACRO-ATTR)
アイテムを宣言するマクロは、それぞれに属性を付与できるようになっているべきです。 よくある例は特定のアイテムをcfgで条件コンパイルするような場合です。
# #![allow(unused_variables)] #fn main() { bitflags! { struct Flags: u8 { #[cfg(windows)] const ControlCenter = 0b001; #[cfg(unix)] const Terminal = 0b010; } } #}
構造体型や列挙型を生成するマクロも、deriveを使えるように属性をサポートすべきです。
# #![allow(unused_variables)] #fn main() { bitflags! { #[derive(Default, Serialize)] struct Flags: u8 { const ControlCenter = 0b001; const Terminal = 0b010; } } #}
アイテムを宣言するマクロがアイテムを宣言できる場所のどこでも使える (C-ANYWHERE)
Rustではアイテムをモジュールレベルから関数のような狭いスコープまで配置することができます。 アイテムを宣言するマクロも同様に、いずれの場所でも動くようになっているべきです。 そして、少なくともモジュールレベルおよび関数レベルでマクロを呼び出すテストを追加すべきです。
# #![allow(unused_variables)] #fn main() { #[cfg(test)] mod tests { test_your_macro_in_a!(module); #[test] fn anywhere() { test_your_macro_in_a!(function); } } #}
モジュールスコープでは動くものの、関数スコープでは動かないマクロの簡単な例を挙げます。
# #![allow(unused_variables)] #fn main() { macro_rules! broken { ($m:ident :: $t:ident) => { pub struct $t; pub mod $m { pub use super::$t; } } } broken!(m::T); // 問題なくTおよびm::Tに展開される fn g() { broken!(m::U); // コンパイルに失敗する、super::Uがgではなく外側のモジュールの方を指すため } #}
アイテムを宣言するマクロが可視性の指定をサポートしている (C-MACRO-VIS)
マクロによって宣言されるアイテムの可視性は、Rustの可視性の構文に沿ってください。
デフォルトではプライベートで、pub
が指定されたらパブリックにします。
# #![allow(unused_variables)] #fn main() { bitflags! { struct PrivateFlags: u8 { const A = 0b0001; const B = 0b0010; } } bitflags! { pub struct PublicFlags: u8 { const C = 0b0100; const D = 0b1000; } } #}
型の指定が柔軟である (C-MACRO-TY)
マクロが$t:ty
のようにして型を受け取る場合、
以下の全てに対応できるべきです。
- プリミティブ型:
u8
,&str
- 相対パス:
m::Data
- 絶対パス:
::base::Data
- 上位を参照する相対パス:
super::Data
- ジェネリクス:
Vec<String>
これができないマクロの簡単な例を挙げます。 次のマクロはプリミティブ型や絶対パスではうまく動きますが、相対パスでは動きません。
# #![allow(unused_variables)] #fn main() { macro_rules! broken { ($m:ident => $t:ty) => { pub mod $m { pub struct Wrapper($t); } } } broken!(a => u8); // okay broken!(b => ::std::marker::PhantomData<()>); // okay struct S; broken!(c => S); // fails to compile #}
ドキュメンテーション
クレートレベルにコード例付きの詳細なドキュメントがある (C-CRATE-DOC)
RFC 1687を参照してください。
全てのアイテムにコード例が付いている (C-EXAMPLE)
全てのパブリックなモジュール、トレイト、構造体型、列挙型、関数、メソッド、マクロ、 およびtypeエイリアスに、その機能を示すコード例を含むrustdocドキュメントが存在するべきです。
この指針は無理のない範囲で適用してください。
適用可能な、その他のアイテムにおけるコード例へのリンクでも十分でしょう。 例えば、ある関数がある型を使うとして、 コード例が関数と型のどちらか一方にあれば、もう一方からはリンクするだけで十分です。
コード例の目的がどのようにそのアイテムを使うかを示すことであるとは限りません。 読者は関数の呼び出し方、列挙型に対するmatchの使い方といった基本的な事柄は理解していると期待できます。 ですから、コード例の目的はなぜアイテムを使うべきかの提示であることも多くあります。
// これはclone()を使うコード例の不味い例です。 // *どう*clone()を呼ぶのか機械的に示しているだけであり、 // *なぜ*これを使うべきかがまったく示されていません。 fn main() { let hello = "hello"; hello.clone(); }
コード例がtry!
やunwrap
ではなく?
を使っている (C-QUESTION-MARK)
ユーザがコード例を丸写しすることは、その良し悪しは別としてよくあることです。 そして、エラーをunwrapするか否かという判断はユーザが自覚的に判断すべきことです。
エラー処理を含むコード例を構成する一般的な方法を以下に示します。
#
で始まる行はcargo test
によってコンパイルされますが、
ユーザに見えるrustdocには表示されません。
/// ```rust
/// # use std::error::Error;
/// #
/// # fn try_main() -> Result<(), Box<Error>> {
/// your;
/// example?;
/// code;
/// #
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
関数のドキュメントにエラー、パニック、安全性に関する事項が含まれている (C-FAILURE)
エラーとなる条件を"Errors"セクションに記載するべきです。 これはトレイトメソッドに対しても同様です。エラーを返す可能性のあるトレイトメソッドには "Errors"セクションを含んだドキュメントを付けるべきです。
標準ライブラリの例を挙げると、トレイトメソッドstd::io::Read::read
の実装のいくつかは
エラーを返す可能性があります。
/// Pull some bytes from this source into the specified buffer, returning
/// how many bytes were read.
///
/// ... lots more info ...
///
/// # Errors
///
/// If this function encounters any form of I/O or other error, an error
/// variant will be returned. If an error is returned then it must be
/// guaranteed that no bytes were read.
また、パニックを起こす条件を"Panics"セクションに記載するべきです。 これはトレイトメソッドに対しても同様です。パニックを起こす可能性のあるトレイトメソッドには "Panics"セクションを含んだドキュメントを付けるべきです。
標準ライブラリを例にすると、パニックを起こす可能性のあるメソッドとしてVec::insert
が挙げられます。
/// Inserts an element at position `index` within the vector, shifting all
/// elements after it to the right.
///
/// # Panics
///
/// Panics if `index` is out of bounds.
あらゆるパニックの可能性を網羅する必要はありません。
特に、呼び出し側の提供したロジックの内部でパニックが起こる場合、
例えば以下のような事例でDisplay
のパニックをドキュメントに記載するのは過剰です。
ですが、微妙なケースではできる限り多くの可能性を網羅する方が良いでしょう。
# #![allow(unused_variables)] #fn main() { /// # Panics /// /// This function panics if `T`'s implementation of `Display` panics. pub fn print<T: Display>(t: T) { println!("{}", t.to_string()); } #}
unsafeな関数のドキュメントには、 その関数を正しく使うために呼び出し側が守らなければならない不変条件を記載した "Safety"セクションを含めてください。
例えば、unsafeな関数であるstd::ptr::read
は以下の事項を呼び出し側に要求しています。
/// Reads the value from `src` without moving it. This leaves the
/// memory in `src` unchanged.
///
/// # Safety
///
/// Beyond accepting a raw pointer, this is unsafe because it semantically
/// moves the value out of `src` without preventing further usage of `src`.
/// If `T` is not `Copy`, then care must be taken to ensure that the value at
/// `src` is not used before the data is overwritten again (e.g. with `write`,
/// `zero_memory`, or `copy_memory`). Note that `*src = foo` counts as a use
/// because it will attempt to drop the value previously at `*src`.
///
/// The pointer must be aligned; use `read_unaligned` if that is not the case.
文章に関係する項目へのリンクを含める (C-LINK)
同じ型の別のメソッドへのリンクはふつう次のようになります。
[`serialize_struct`]: #method.serialize_struct
別の型へのリンクはこうなります。
[`Deserialize`]: trait.Deserialize.html
親・子モジュールへのリンクは次のようにします。
[`Value`]: ../enum.Value.html
[`DeserializeOwned`]: de/trait.DeserializeOwned.html
この指針はRFC 1574の"Link all the things"によって公式に推奨されています。
Cargo.tomlが一般的なメタデータを全て含んでいる (C-METADATA)
Cargo.toml
の[package]
セクションは以下の値を含むべきです。
authors
description
license
repository
readme
keywords
categories
加えて、2つの任意項目があります。
documentation
homepage
デフォルトで、crates.ioはdocs.rs上のドキュメントへリンクします。
documentation
メタデータはdocs.rs以外の場所でドキュメントをホストしている場合にのみ必要です。
例えば、そのクレートがdocs.rsのビルド環境に存在しない共有ライブラリを要求している場合などです。
homepage
メタデータはソースレポジトリやAPIドキュメント以外の独自のウェブサイトがある場合のみ
使用されるべきです。documentation
やrepository
と重複した値を入れないでください。
例えば、Serdeはhomepage
を専用のウェブサイトであるhttps://serde.rsに設定しています。
html_root_url属性が設定されている (C-HTML-ROOT)
docs.rsを主なAPIドキュメントとして使っているならば"https://docs.rs/CRATE/MAJOR.MINOR.PATCH"
に設定してください。
html_root_url
属性は、rustdocが下流クレートをコンパイルする際にどのようにURLを作成するべきかを伝えるものです。
これが無ければ、あなたのクレートに依存するクレートにおいて、ドキュメント中のリンクが間違ったものになります。
# #![allow(unused_variables)] #![doc(html_root_url = "https://docs.rs/log/0.3.8")] #fn main() { #}
バージョンがURLに含まれていることから分かる通り、
Cargo.toml
内のバージョン番号と同期させる必要があります。
html_root_url
がクレートのバージョンとずれた場合に失敗するインテグレーションテストを追加する
version-sync
クレートが役に立つはずです。
この方式が気に入らなければ、Cargo.toml
のバージョンの所にメモ書きを残しておくとよいでしょう。
version = "0.3.8" # html_root_urlの更新 忘れない
docs.rs以外にドキュメントをホストしているなら、html_root_url
の値にクレート名 + index.htmlを付け足すと
あなたのクレートのルートモジュールにたどり着けるように設定してください。
例えば、ルートモジュールが"https://api.rocket.rs/rocket/index.html"
にあるならば、
html_root_url
は"https://api.rocket.rs"
とすべきです。
大きな変更が全てリリースノートに記載されている (C-RELNOTES)
クレートのユーザはリリースノートを読むことでバージョン間の変更点を知ることができます。 クレートレベルのドキュメントまたはCargo.tomlからリンクされたリポジトリにリリースノートへのリンクを置いてください。
破壊的変更(RFC 1105で定義されている)は明確に区別されているべきです。
Gitを用いてソースコードの履歴を管理しているなら、 crates.ioに発行された全てのリリースは対応するコミットにタグを付けてください。 Git以外のVCSでも同様の処理をしておくべきです。
# Tag the current commit
GIT_COMMITTER_DATE=$(git log -n1 --pretty=%aD) git tag -a -m "Release 0.3.0" 0.3.0
git push --tags
アノテーション付きタグが一つでも存在するとアノテーションのないタグを無視するGitコマンドがあるため、 アノテーション付きタグの使用が推奨されます。
例
Rustdocはユーザがそのクレートを使用するのに必要な情報を網羅すべきですが、 それ以上の内容は含むべきではありません。 文章中で関係のある実装詳細を解説するのはよいですが、 実際にドキュメント項目として現れてはいけません。
特に、どのようなimplがドキュメントに表示されるかに関しては精選するようにしてください。
ユーザがクレートを使用するのに必要なものだけ示し、それ以外は隠しましょう。
以下のコードでは、PublicError
のドキュメントにFrom<PrivateError>
が表示されてしまいます。
ユーザがPrivateError
型を扱うことはないため、この項目はユーザにとっては関係ないものです。
なので、#[doc(hidden)]
を使って隠します。
# #![allow(unused_variables)] #fn main() { // This error type is returned to users. pub struct PublicError { /* ... */ } // This error type is returned by some private helper functions. struct PrivateError { /* ... */ } // Enable use of `?` operator. #[doc(hidden)] impl From<PrivateError> for PublicError { fn from(err: PrivateError) -> PublicError { /* ... */ } } #}
pub(crate)
も実装詳細をパブリックなAPIから隠す便利なツールです。
同じモジュール以外からもアイテムを使えるようになりますが、他のクレートからは見えません。
予測性
スマートポインタがinherentメソッドを持っていない (C-SMART-PTR)
例えば、Box::into_raw
は次のように定義されています。
# #![allow(unused_variables)] #fn main() { impl<T> Box<T> where T: ?Sized { fn into_raw(b: Box<T>) -> *mut T { /* ... */ } } let boxed_str: Box<str> = /* ... */; let ptr = Box::into_raw(boxed_str); #}
もしこれがinherentメソッドであったら、呼び出そうとしているメソッドがT
のものなのか
Box<T>
のものなのか区別が付かなくなります。
# #![allow(unused_variables)] #fn main() { impl<T> Box<T> where T: ?Sized { // Do not do this. fn into_raw(self) -> *mut T { /* ... */ } } let boxed_str: Box<str> = /* ... */; // スマートポインタのDerefを経由してstrのメソッドにアクセスしている boxed_str.chars() // これは`Box<str>`のメソッド……? boxed_str.into_raw() #}
変換メソッドが最も関係の深い型に付いている (C-CONV-SPECIFIC)
迷ったら_from
よりもto_
/as_
/into_
を選んでください。
後者の方がより使いやすく、また他のメソッドにチェーンすることもできるからです。
2つの型の間の変換において、多くの場合どちらか一方が明らかに特徴的です。
すなわち、他方にはない不変条件や解釈が追加されています。
例えばstr
はUTF-8でエンコードされたバイト列ですから、単なるバイト列である&[u8]
より特徴的です。
変換メソッドは、関係する型の中で、より特徴的なものが持つべきです。
従って、str
はas_bytes
メソッド及びfrom_utf8
コンストラクタを持つのです。
この方が直感的であるだけでなく、&[u8]
のような型が無数の変換メソッドで汚染されていくという事態が避けられます。
明確なレシーバを持つ関数がメソッドになっている (C-METHOD)
特定の型と強く関連した操作についてはメソッドにしてください。
# #![allow(unused_variables)] #fn main() { impl Foo { pub fn frob(&self, w: widget) { /* ... */ } } #}
関数にしてはいけません。
# #![allow(unused_variables)] #fn main() { pub fn frob(foo: &Foo, w: widget) { /* ... */ } #}
関数でなくメソッドを選ぶことには多数の利点があります。
- インポートしたり関数へのパスを記述したりする必要がない。その型の値さえあれば必要な操作ができます。
- 呼び出し時に自動借用が働きます。 (可変借用も含めて)
- 「この型
T
で何ができるんだろう」という疑問への答えが簡単になります。 (特にrustdocを使用している場合) self
記法が使われるため、より簡潔かつ明白に所有権の区別が示されます。
関数がoutパラメータを持たない (C-NO-OUT)
例えば複数のBar
を返すときはこのようにしてください。
# #![allow(unused_variables)] #fn main() { fn foo() -> (Bar, Bar) #}
このようにoutパラメータのようなものを取ってはいけません。
# #![allow(unused_variables)] #fn main() { fn foo(output: &mut Bar) -> Bar #}
タプルや構造体を使って複数の値を返しても効率のよいコードにコンパイルされますし、 ヒープの確保も行われません。複数の値を返す必要があるならこれらの型を利用すべきです。
例外は関数が呼び出し側の所有するデータを変更する場合です。 例えば、バッファの再利用をする場合は次のようになるでしょう。
# #![allow(unused_variables)] #fn main() { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> #}
奇妙な演算子オーバーロードを行っていない (C-OVERLOAD)
組み込みの演算子(*
や|
など)はstd::ops
にあるトレイトを実装することで使えるようになります。
これらの演算子には元から意味が付与されています。
例えば、Mul
は乗算のような(そして結合性などの特性を共有した)演算にのみ実装されるべきです。
Deref
とDerefMut
を実装しているのはスマートポインタだけである (C-DEREF)
Deref
トレイトはコンパイラによって様々な状況で暗黙的に使われ、メソッドの解決と関わります。
その周辺の規則はスマートポインタを念頭において設計されているため、
これらのトレイトはスマートポインタに対してのみ実装されるべきです。
標準ライブラリでの例
コンストラクタはスタティックなinherentメソッドである (C-CTOR)
Rustにおいて、「コンストラクタ」は単なる慣習に過ぎません。 コンストラクタの命名には様々な慣習があり、その区別が分かり辛いことが多々あります。
最も基本的なコンストラクタの形は引数のないnew
メソッドです。
# #![allow(unused_variables)] #fn main() { impl<T> Example<T> { pub fn new() -> Example<T> { /* ... */ } } #}
コンストラクタは、その生成する型のスタティック(self
を取らない)なinherentメソッドです。
型をインポートする慣習と併せれば、分かりやすく簡潔にその型を生成することができます。
# #![allow(unused_variables)] #fn main() { use example::Example; // Construct a new Example. let ex = Example::new(); #}
new
という名前は、1つ目の最も重要なコンストラクタに使われるべきです。
上記の例のように引数を取らないこともあれば、Box
に入れる値を取るBox::new
のように、
引数を取ることもあります。
主にI/Oリソースを表す型では、File::open
、Mmap::open
、
TcpStream::connect
、あるいは UpdSocket::bind
のように
コンストラクタの命名が異なっていることがあります。
これらは、各々の領域で適した名前が選ばれています。
ある型の値を生成する方法が複数存在することは多くあります。
そういった場合、2個目以降のコンストラクタには_with_foo
などと名前の最後に付けることが一般的です。
例えば、Mmap::open_with_offset
などです。
複数のオプションがあるならばビルダーパターン(C-BUILDER)の使用も考えてください。
別の型の値を取って変換を行うコンストラクタというものもあります。
それらはstd::io::Error::from_raw_os_error
のように、一般にfrom_
から始まる名前を持ちます。
ここで、よく似たものにFrom
トレイト(C-CONV-TRAITS)が存在します。
from_
の付いた変換コンストラクタとFrom<T>
実装の間には3つの相違点があります。
from_
コンストラクタはunsafeにすることができますが、From
の実装ではできません。 例:Box::from_raw
。from_
コンストラクタは、u64::from_str_radix
のように 元になるデータを区別するための追加の引数を取ることができます。From
実装は元のデータから出力の型のエンコード方法を決定できる場合にのみ適しています。u64::from_be
やString::from_utf8
のように入力の型が単なるデータ列であるとき、 コンストラクタの名前によってその意味を伝えることが可能です。
標準ライブラリでの例
std::io::Error::new
はIOエラーの生成に使われるコンストラクタstd::io::Error::from_raw_os_error
は OSから与えられたエラーコードを変換するコンストラクタBox::new
は引数を1つとり、コンテナ型を生成するコンストラクタFile::open
はファイルをオープンするMmap::open_with_offset
は指定されたオプションでメモリーマップをオープンする
柔軟性
重複した処理を行わなくて済むように中間生成物を公開している (C-INTERMEDIATE)
何らかの処理を行って結果を返す関数の多くは、関連したデータを途中で生成しています。 もしユーザにとって有益なものがあれば、それを公開するAPIの追加を検討してください。
標準ライブラリでの例
-
Vec::binary_search
は目的の値が見つかったか否かをbool
で表したり、 目的の値が見つかった位置をOption<usize>
で返すようにはなっていません。 代わりに、もし見つかればその位置を、そして見つからなければその値が挿入されるべき位置を返します。 -
String::from_utf8
は入力が正しいUTF-8でなければ失敗します。 そのとき、入力のどこまでが正しいUTF-8であったかを返し、また入力されたバイト列の所有権も返します。 -
HashMap::insert
はそのキーの場所に元から値が存在していたら、その値をOption<T>
で返します。 ユーザがこの値を必要としているとき、この挙動がなければハッシュテーブルへのルックアップを二度繰り返さなければなりません。
呼び出し側がデータをコピーするタイミングを決める (C-CALLER-CONTROL)
引数の所有権を要する関数は、借用して複製するのではなく所有権を受け取るべきです。
# #![allow(unused_variables)] #fn main() { // 良い例: fn foo(b: Bar) { /* use b as owned, directly */ } // 悪い例: fn foo(b: &Bar) { let b = b.clone(); /* use b as owned after cloning */ } #}
もし関数が引数の所有権を必要としないのなら、 所有権を取って最終的にdropする代わりに可変あるいは非可変借用を受け取るべきです。
# #![allow(unused_variables)] #fn main() { // 良い例: fn foo(b: &Bar) { /* use b as borrowed */ } // 悪い例: fn foo(b: Bar) { /* use b as borrowed, it is implicitly dropped before function returns */ } #}
Copy
トレイトは本当に必要な場合のみ使用してください。
単に低コストでコピーが可能であると伝えるために使用してはいけません。
ジェネリクスを用いて関数の引数に対する制限を最小にしている (C-GENERIC)
関数の引数に対する制約が少ないほど、その関数は幅広く使えるようになります。
単にイテレーションが必要なだけであれば、このようにジェネリクスを用いてください。
# #![allow(unused_variables)] #fn main() { fn foo<I: IntoIterator<Item = i64>>(iter: I) { /* ... */ } #}
特定の型を指定しないでください。
# #![allow(unused_variables)] #fn main() { fn foo(c: &[i64]) { /* ... */ } fn foo(c: &Vec<i64>) { /* ... */ } fn foo(c: &SomeOtherCollection<i64>) { /* ... */ } #}
もっと言えば、ジェネリクスを用い、関数の引数に対して必要な制約を正確に示してください。
ジェネリクスの利点
-
再利用性。ジェネリックな関数は受け取る型への明確な要件を示しつつ、 多くの型に対して適用できます。
-
スタティックディスパッチと最適化。ジェネリック関数を呼び出すと特定の型に特殊化("monomorphized")されます。 トレイトメソッドの呼び出しはスタティックかつ実装を直接呼び出す形に変換され、 コンパイラは呼び出しをインライン化し最適化することが可能です。
-
レイアウトのインライン化。構造体型や列挙型がジェネリックな型
T
を持つとき、 型T
の値へは間接的なアクセスを挟むことなく、構造体型や列挙型の内部にインライン化されます。 -
推論。ジェネリック関数の型パラメータは大抵推論が可能であるため、 明示的な変換やその他のメソッド呼び出しといったコード中の冗長な部分を削減することが可能です。
-
正確な型。ジェネリクスによって型に名前を与えられるため、 正確にその型を受け取りあるいは生成する箇所を指定することができます。 例えば、次の関数は全く同じ型
T
を受け取り、返すことが保証されます。Trait
を実装する異なる型を用いて呼び出したりすることはできません。# #![allow(unused_variables)] #fn main() { fn binary<T: Trait>(x: T, y: T) -> T #}
ジェネリクスの欠点
-
コードサイズ。ジェネリック関数の特殊化により、関数の中身は複製されます。 コードサイズの増大がパフォーマンスと釣り合うかどうか検討するべきです。
-
一様な型。これは型が正確であることの裏返しです。型パラメータ
T
は一つの実際の型をもちます。 例えばVec<T>
は単一の具体型のコレクションです(そして内部的にはメモリ上に隣り合って並べられます)。 一様でないコレクションが有用な場合もあります。trait objectsを参照してください。 -
関数の定義の複雑化。ジェネリクスを多用すると、関数の定義を読んだり理解することが難しくなります。
標準ライブラリの例
std::fs::File::open
はAsRef<Path>
というジェネリックな型を取ります。 これにより、文字列リテラル"f.txt"
やPath
、あるいはOsString
などを渡して ファイルを開くことができます。
トレイトオブジェクトとして有用なトレイトがオブジェクトセーフになっている (C-OBJECT)
トレイトオブジェクトには大きな制約があります。それは、トレイトオブジェクト経由で呼ばれるメソッドは
ジェネリクスを使用できず、またSelf
をレシーバ以外の引数で使用できないことです。
トレイトを設計する際、そのトレイトがオブジェクトとして使用されるのか ジェネリクスの境界として使用されるのかを決めておく必要があります。
そのトレイトがオブジェクトとして使用されることを念頭に置くならば、 トレイトメソッドではジェネリクスの代わりにトレイトオブジェクトを使用すべきです。
Self: Sized
というwhere
節を用いることで、特定のメソッドをトレイトオブジェクトから除外することができます。
次のトレイトはジェネリックなメソッドを持つためオブジェクトセーフではありません。
# #![allow(unused_variables)] #fn main() { trait MyTrait { fn object_safe(&self, i: i32); fn not_object_safe<T>(&self, t: T); } #}
ジェネリックなメソッドにSelf: Sized
を要求させることでトレイトオブジェクトから除外し、
そのトレイトをオブジェクトセーフにすることが可能です。
# #![allow(unused_variables)] #fn main() { trait MyTrait { fn object_safe(&self, i: i32); fn not_object_safe<T>(&self, t: T) where Self: Sized; } #}
トレイトオブジェクトの利点
- 一様性。これ無しでは解決できない問題もあります。
- コードサイズ。ジェネリクスと異なり、トレイトオブジェクトは特殊化(monomorphized)されたコードを生成しないため、 コードサイズを大幅に削減することができます。
トレイトオブジェクトの欠点
- ジェネリックなメソッドが使えない。トレイトオブジェクトは今のところジェネリックなメソッドを 持つことができません。
- 動的ディスパッチとファットポインタ。トレイトオブジェクトは、パフォーマンスに影響する可能性のある 間接アクセスと仮想関数テーブルによるディスパッチを引き起こします。
- Selfが使えない。メソッドのレシーバ引数を除いて
Self
型を取ることはできません。
標準ライブラリでの例
io::Read
とio::Write
は頻繁にオブジェクトとして使われます。Iterator
には、トレイトオブジェクトとして使用できるようにするため、Self: Sized
が指定されたジェネリックなメソッドがあります。
型安全性
newtypeを使って静的に値を区別する (C-NEWTYPE)
実際の型は同じでも、newtypeを用いることでその異なる解釈の間を静的に区別することができます。
例えば、f64
の値はマイルとキロメートルの何れかの量を示しているかもしれません。
newtypeを使うことで、どちらが正しい解釈なのかを示すことが可能です。
# #![allow(unused_variables)] #fn main() { struct Miles(pub f64); struct Kilometers(pub f64); impl Miles { fn to_kilometers(self) -> Kilometers { /* ... */ } } impl Kilometers { fn to_miles(self) -> Miles { /* ... */ } } #}
このように型を分けることで、混ざってしまわないことを静的に保証できます。 例えば、
# #![allow(unused_variables)] #fn main() { fn are_we_there_yet(distance_travelled: Miles) -> bool { /* ... */ } #}
は間違ってKilometers
の値で呼ばれることはありません。
コンパイラが適切な変換を施すことを思い出させてくれるため、恐ろしいバグも回避できます。
bool
やOption
の代わりに意味のある型を使っている (C-CUSTOM-TYPE)
こうして下さい。
# #![allow(unused_variables)] #fn main() { let w = Widget::new(Small, Round) #}
次は駄目な例です。
# #![allow(unused_variables)] #fn main() { let w = Widget::new(true, false) #}
bool
やu8
、Option
のような汎用的な型には様々な解釈が考えられます。
専用の型(列挙型、構造体、あるいはタプル)を用い、その意味と不変条件を伝えるようにしてください。
上記の駄目な例では、引数の名前を調べない限りtrue
とfalse
がどういった意味なのか分かりません。
Small
やRound
といった型を使った場合ではその意味が明確になっています。
専用の型を使うことで今後の拡張も楽になります。上記の例で言うと、
ここにExtraLarge
というバリアントを追加したりできますね。
既存の型にゼロコストで区別された名前を付ける方法についてはnewtypeパターン(C-NEWTYPE)を参照してください。
フラグの集合を列挙型ではなくbitflags
で表している (C-BITFLAG)
Rustではenum
において値を明示することが可能です。
# #![allow(unused_variables)] #fn main() { enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, } #}
値の指定は他のシステムや言語向けに整数値としてシリアライゼーションしないといけない場合に便利です。
数値でなくColor
を関数が取れば、数値への変換が可能であると同時に不正な値の入力が防げるため
「型安全性」に貢献します。
enum
は複数の選択肢のうちの1つを要求するAPIに使われます。
しかし、APIの入力がフラグの集合である場合もあります。
C言語のコードでは各々のフラグを特定のビットに割当てることで、1つの整数値によって32あるいは64等のフラグを表せるようにします。
Rustではbitflags
クレートがこのパターンの型安全な実装を提供しています。
#[macro_use] extern crate bitflags; bitflags! { struct Flags: u32 { const FLAG_A = 0b00000001; const FLAG_B = 0b00000010; const FLAG_C = 0b00000100; } } fn f(settings: Flags) { if settings.contains(FLAG_A) { println!("doing thing A"); } if settings.contains(FLAG_B) { println!("doing thing B"); } if settings.contains(FLAG_C) { println!("doing thing C"); } } fn main() { f(FLAG_A | FLAG_C); }
複雑な値の生成にビルダーパターンを使っている (C-BUILDER)
以下のような理由によって、データ構造の生成に複雑な手順が必要なことがあります。
- 多数の入力がある
- 複合的なデータである (例えばスライス)
- オプショナルな設定データがある
- 選択肢が複数ある
こんなとき、異なった引数をもつ多数のコンストラクタを追加してしまいがちです。
そういったデータ構造T
があるとき、T
に ビルダー を用意することを検討してください。
- インクリメンタルに
T
を設定するための別のデータ型、TBuilder
を作ります。 可能であれば、より明瞭な名前を選んでください。例えば、子プロセスを作成するビルダーのCommand
、Url
を作成するビルダーのParseOptions
のようにです。 - ビルダー自体のコンストラクタは、
T
を生成するために 必須 のパラメータのみ取るべきです。 - ビルダーは設定を行うための便利なメソッドを提供するべきです。
例えば、複合的なデータをインクリメンタルに設定できるようなものです。
メソッドは
self
を返して、メソッドチェーンを行えるようにすべきです。 - ビルダーは実際に
T
の値を生成する「終端」メソッドを1つ以上提供すべきです。
ビルダーパターンは特に、T
の生成においてプロセスやタスクの立ち上げのような副作用が生じる場合にも適しています。
Rustでのビルダーパターンの作り方には、所有権の取り扱いによって以下で示す2通りの方法があります。
非消費ビルダー (推奨)
最終的なT
の生成にビルダー自身を必要としない場合があります。
例としてstd::process::Command
を挙げます。
# #![allow(unused_variables)] #fn main() { // NOTE: 実際のCommand APIはStringを所有しません。 // これは単純化されたバージョンです pub struct Command { program: String, args: Vec<String>, cwd: Option<String>, // etc } impl Command { pub fn new(program: String) -> Command { Command { program: program, args: Vec::new(), cwd: None, } } /// Add an argument to pass to the program. pub fn arg(&mut self, arg: String) -> &mut Command { self.args.push(arg); self } /// Add multiple arguments to pass to the program. pub fn args(&mut self, args: &[String]) -> &mut Command { self.args.extend_from_slice(args); self } /// Set the working directory for the child process. pub fn current_dir(&mut self, dir: String) -> &mut Command { self.cwd = Some(dir); self } /// Executes the command as a child process, which is returned. pub fn spawn(&self) -> io::Result<Child> { /* ... */ } } #}
ビルダーの設定データを使って実際にプロセスを立ち上げるspawn
メソッドが、
ビルダーを不変参照で受け取っていることに注目してください。
これはプロセスの立ち上げにビルダーの設定データの所有権が必要ないからです。
終端メソッドであるspawn
が参照しか必要としないのですから、
設定メソッドはself
を参照で受け取り、返すべきです。
利点
借用を使うことで、Command
はワンライナーでも、もっと複雑なことをする場合でも
どちらにでも使うことができます。
# #![allow(unused_variables)] #fn main() { // One-liners Command::new("/bin/cat").arg("file.txt").spawn(); // Complex configuration let mut cmd = Command::new("/bin/ls"); cmd.arg("."); if size_sorted { cmd.arg("-S"); } cmd.spawn(); #}
消費ビルダー
ビルダーがT
を生成する際に、所有権を移動しなければならない場合があります。
つまり終端メソッドが&self
ではなくself
を取るときです。
# #![allow(unused_variables)] #fn main() { impl TaskBuilder { /// Name the task-to-be. pub fn named(mut self, name: String) -> TaskBuilder { self.name = Some(name); self } /// Redirect task-local stdout. pub fn stdout(mut self, stdout: Box<io::Write + Send>) -> TaskBuilder { self.stdout = Some(stdout); self } /// Creates and executes a new child task. pub fn spawn<F>(self, f: F) where F: FnOnce() + Send { /* ... */ } } #}
ここで、stdout
を設定するときにはio::Write
を実装する型の所有権が渡されるため、
タスクの生成を行う際にその所有権を移動する必要があります(spawn
内)。
ビルダーの終端メソッドが所有権を要求するとき、大きなトレードオフが存在します。
-
もし他のビルダーメソッドが可変借用を受け渡すと、 前述した複雑な設定の場合は上手く動きますが、ワンライナーでの設定は不可能になります。
-
もし他のビルダーメソッドが
self
の所有権を受け渡すと、 ワンライナーはそのまま動きますが、複雑な設定を行う際には不便です。
簡単なものは簡単なまま保つべきですから、
消費ビルダーのビルダーメソッドは全てself
を取り、返すべきです。
このビルダーを使用するコードは次のようになります。
# #![allow(unused_variables)] #fn main() { // One-liners TaskBuilder::new("my_task").spawn(|| { /* ... */ }); // Complex configuration let mut task = TaskBuilder::new(); task = task.named("my_task_2"); // must re-assign to retain ownership if reroute { task = task.stdout(mywriter); } task.spawn(|| { /* ... */ }); #}
所有権がspawn
に消費されるまで各メソッド間で次々と受け渡され、ワンライナーは今までどおり動きます。
一方で、複雑な設定は記述量が増えています。各メソッドを呼ぶ際に再束縛が必要になります。
信頼性
関数が引数を検証している (C-VALIDATE)
RustでのAPIは「送るものに関しては厳密に、受け取るものに関しては寛容に」という堅牢性原則には縛られません。
代わりに、Rustコードは可能なかぎり入力の正しさを 検証 すべきです。
この検証は、以下のようにして行うことができます(より推奨されるものの順に並んでいます)。
静的な検証
不正な値を受け付けないよう引数の型を選んでください。
例えば次のようにします。
# #![allow(unused_variables)] #fn main() { fn foo(a: Ascii) { /* ... */ } #}
このようにしてはいけません。
# #![allow(unused_variables)] #fn main() { fn foo(a: u8) { /* ... */ } #}
ここでAscii
はu8
の ラッパ であり、最上位ビットがゼロであることを保証します。
型安全なラッパを作る方法はnewtypeパターン(C-NEWTYPE)を参照してください。
静的な検証は型の境界にコストを押し込む(例えば、u8
はAscii
に変換しなければ受け付けない)ため、
実行時コストが掛かることは余りありません。 また、実行時ではなくコンパイル中にバグが検出されます。
一方、型を用いて表すことが困難あるいは不可能な特性も存在します。
動的な検証
入力を処理と同時に(あるいは必要ならば事前に)検証します。 動的な検証は静的な検証よりも実装が簡単ですが、いくつかの欠点があります。
- 実行時コスト (処理と検証を同時に行うことができない場合)
- バグの検出が遅れます
- パニックや
Result
/Option
型による失敗ケースを呼び出し側のコードで処理しなければなりません
debug_assert!
による動的な検証
プロダクションビルドにおいて高コストな検証を行わないようにできるかもしれません。
動的な検証のオプトアウト
チェックを行わないバージョンの関数を追加します。
チェックを行わない関数の名前の後ろに_unchecked
と付けたり、raw
という名前のモジュールに置かれたりするのが一般的です。
チェックを行わない関数は(1)パフォーマンスがチェックよりも優先される場合 (2)入力が正しいと呼び出し側が確信している場合に使うことができます。
デストラクタが失敗しない (C-DTOR-FAIL)
デストラクタはパニック時にも実行されますが、その際にさらにデストラクタ内でパニックすると プログラムは強制終了します。
デストラクタでパニックする代わりに、Result
を返して失敗を通知するclose
メソッドのような、
失敗を確認することのできる破棄メソッドを追加してください。
ブロックする可能性のあるデストラクタには代替手段を用意する (C-DTOR-BLOCK)
デバッグが難しくなるため、デストラクタでブロックするような操作を行うべきではありません。 ブロックせずに破棄を行える別のメソッドを追加するべきです。
デバッガビリティ
全てのパブリックな型にDebug
を実装する (C-DEBUG)
例外が必要なことは稀なはずです。
Debug
表現を空にしない (C-DEBUG-NONEMPTY)
Debug
表現は空にするべきではありません。概念的に空である値に対しても同様です。
# #![allow(unused_variables)] #fn main() { let empty_str = ""; assert_eq!(format!("{:?}", empty_str), "\"\""); let empty_vec = Vec::<bool>::new(); assert_eq!(format!("{:?}", empty_vec), "[]"); #}
将来性
sealedトレイトを使って下流の実装を適切に防いでいる (C-SEALED)
そのクレート内でのみ実装されることを想定したトレイトについて、 sealedトレイトパターンを用いることでユーザのコードを壊すことなしに変更を加えることが可能になります。
# #![allow(unused_variables)] #fn main() { /// このトレイトはsealされているため、他のクレートで実装を追加することはできません。 pub trait TheTrait: private::Sealed { // メソッド fn ...(); // ユーザが呼ぶべきでないプライベートメソッド #[doc(hidden)] fn ...(); } // 実装 impl TheTrait for usize { /* ... */ } mod private { pub trait Sealed {} // 同じ型に実装 impl Sealed for usize {} } #}
プライベートな空のSealed
親トレイトを下流のクレートから参照することはできません。
従って、Sealed
(そしてTheTrait
)の実装はこのクレート内にのみ存在できます。
トレイトにメソッドを追加することは一般的に破壊的変更となりますが、
sealedトレイトであるTheTrait
にメソッドを追加することは破壊的変更になりません。
また、ドキュメントに掲載されていないメソッドの定義も自由に変更することができます。
sealedトレイトからパブリックなメソッドを取り除いたり、 定義を変更したりすることは依然として破壊的変更であることに注意してください。
混乱したユーザがそれらのトレイトを実装しようとすることを防ぐため、 そのトレイトはsealされており、他のクレートから実装されるべきものではないことを ドキュメントに記載しておくべきです。
例
構造体のフィールドを適切にプライベートにする (C-STRUCT-PRIVATE)
構造体のフィールドをパブリックにすることには重大な責任が伴います。 表現を変更することはできなくなり、またユーザはフィールドを自由に弄ることができるため 値のバリデーションや不変条件の検証などができなくなります。
パブリックなフィールドはC言語的な意味あいのsturct
、すなわち複合化された受け身のデータ構造には最適ですが、
それ以外ではgetter/setterメソッドを用意しフィールドを隠蔽することを考慮してください。
newtypeを用いて実装詳細を隠蔽している (C-NEWTYPE-HIDE)
newtypeはユーザへの保証を保ちつつ実装詳細を隠蔽するために役立ちます。
例としてこの、イテレータ型を返すmy_transform
関数を見てください。
# #![allow(unused_variables)] #fn main() { use std::iter::{Enumerate, Skip}; pub fn my_transform<I: Iterator>(input: I) -> Enumerate<Skip<I>> { input.skip(3).enumerate() } #}
ユーザから見た際にIterator<Item = (usize, T)>
のように見えるよう型を隠したいときは、
newtype型を使うことができます。
# #![allow(unused_variables)] #fn main() { use std::iter::{Enumerate, Skip}; pub struct MyTransformResult<I>(Enumerate<Skip<I>>); impl<I: Iterator> Iterator for MyTransformResult<I> { type Item = (usize, I::Item); fn next(&mut self) -> Option<Self::Item> { self.0.next() } } pub fn my_transform<I: Iterator>(input: I) -> MyTransformResult<I> { MyTransformResult(input.skip(3).enumerate()) } #}
これにより宣言が簡単になるだけでなく、ユーザへの保証を小さくすることができます。 ユーザは返されたイテレータがどのように生成されたのか、どのような内部表現になっているのかを知ることができません。 したがって、ユーザのコードを壊すこと無く将来的に内部表現を変更できるようになります。
impl Trait
は現在のところunstableですが、将来的にはこれを用いても同じことが達成できるようになります。
# #![allow(unused_variables)] #![feature(conservative_impl_trait)] #fn main() { pub fn my_transform<I: Iterator>(input: I) -> impl Iterator<Item = (usize, I::Item)> { input.skip(3).enumerate() } #}
データ構造にderiveしたトレイトの境界を定義で繰り返さない (C-STRUCT-BOUNDS)
ジェネリックなデータ構造はderiveしたトレイト境界をその定義において繰り返すべきではありません。
derive
属性によって実装されたトレイトは、ジェネリック型がそのトレイトを実装している場合のみ実装される
個別のimpl
ブロックに展開されます。
# #![allow(unused_variables)] #fn main() { // 良い例: #[derive(Clone, Debug, PartialEq)] struct Good<T> { /* ... */ } // 悪い例: #[derive(Clone, Debug, PartialEq)] struct Bad<T: Clone + Debug + PartialEq> { /* ... */ } #}
Bad
のようにderiveしたトレイトを境界として繰り返すのは不要であり、
しかも後方互換性を保つ上で困難となります。
なぜなら、ここでPartialOrd
をderiveした場合を考えてみて下さい。
# #![allow(unused_variables)] #fn main() { // 非破壊的変更: #[derive(Clone, Debug, PartialEq, PartialOrd)] struct Good<T> { /* ... */ } // 破壊的変更: #[derive(Clone, Debug, PartialEq, PartialOrd)] struct Bad<T: Clone + Debug + PartialEq + PartialOrd> { /* ... */ } #}
一般的に、データ構造にトレイト境界を追加すると全ての利用箇所において追加の境界を満たす必要が発生するため、
破壊的変更となります。
しかし、derive
属性を用いて標準ライブラリのトレイトを実装することは破壊的変更となりません。
以下のトレイトはデータ構造において境界とするべきではありません。
Clone
PartialEq
PartialOrd
Debug
Display
Default
Serialize
Deserialize
DeserializeOwned
Read
やWrite
のようなderiveできないトレイトの中には、
厳密にはデータ構造によって要求されないグレーゾーンのものが存在します。
これらは型のふるまいを伝える役に立つ可能性がありますが、一方で将来的な拡張性の障害にもなります。
しかし、deriveできるトレイトを境界に追加するよりは問題が少ないでしょう。
例外
データ構造にトレイト境界が必要となる、3つの例外があります。
- データ構造がトレイトの関連型を参照している。
?Sized
境界。- データ構造がそのトレイト境界を必要とする
Drop
実装を持っている。Rustは現在、Drop
実装の境界がデータ構造自身にもすることを要求します。
標準ライブラリでの例
std::borrow::Cow
はBorrow
トレイトの関連型を参照しています。std::boxed::Box
は暗黙のSized
境界を除いています。std::io::BufWriter
はDrop
実装に必要である境界を型に要求します。
必要事項
stableなクレートのパブリックな依存クレートがstableである (C-STABLE)
全てのパブリックな依存クレートがstable(>=1.0.0)とならない限り、クレートをstableにすることはできません。
パブリックな依存性は、依存先に由来する型がそのクレートのパブリックなAPIに使われているものです。
# #![allow(unused_variables)] #fn main() { pub fn do_my_thing(arg: other_crate::TheirThing) { /* ... */ } #}
この関数を含むクレートは、other_crate
がstableにならない限りstableになりません。
パブリックな依存制は思わぬ所に潜んでいることがあるため、注意が必要です。
# #![allow(unused_variables)] #fn main() { pub struct Error { private: ErrorImpl, } enum ErrorImpl { Io(io::Error), // ErrorImplはプライベートなので、other_crateがstableでなくても問題ないはず。 Dep(other_crate::Error), } // いや、ここでother_crateをパブリックなAPIの一部にしてしまっている。 impl From<other_crate::Error> for Error { fn from(err: other_crate::Error) -> Self { Error { private: ErrorImpl::Dep(err) } } } #}
クレートとその依存先がpermissiveなライセンスの下にある (C-PERMISSIVE)
Rustプロジェクトによって公開されているソフトウェアはMITとApache 2.0 のデュアルライセンス下にあります。 Rustエコシステムとの最大の協調性が必要なクレートは同じようにするべきです。 その他の選択肢も以下に提示します。
Rustのライセンスに関してはRust FAQで幾分かの言及がなされていますが、 このAPIガイドラインでは詳しい説明は行いません。 これはライセンスに関する解説ではなく、Rustとの協調性に関するガイドラインだからです。
Rustと同等のライセンスをあなたのプロジェクトに適用したい場合、次のように
Cargo.toml
中のlicense
フィールドを設定してください。
[package]
name = "..."
version = "..."
authors = ["..."]
license = "MIT/Apache-2.0"
そして、README.mdの最後に以下を追加してください。
## License
Licensed under either of
* Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license
([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
MIT/Apache-2.0のデュアルライセンスの他に、Rustのクレート開発者によってよく使われるのは MITやBSDのようなpermissiveなライセンスを単体で利用することです。 このやり方でも、小さな制約が加わってしまうだけでRustとの互換性はあります。
Rustとの完全なライセンス互換性が必要なら、Apacheライセンス単体を用いることは推奨されません。 Apacheライセンスはpermissiveではありますが、MITやBSDより大きな制約を課しているため、 デュアルライセンスのRust本体は使うことができるにも関わらず Apacheライセンスのみのソフトウェアを使うことのできない状況が存在するからです。
クレートの依存先のライセンスはクレート自身の配布にも制約を与える可能性があるため、 permissiveなライセンスのクレートはpermissiveなライセンスのクレートにのみ依存するべきです。
外部リンク
- RFC 199 - Ownership naming conventions
- RFC 344 - Naming conventions
- RFC 430 - Naming conventions
- RFC 505 - Doc conventions
- RFC 1574 - Doc conventions
- RFC 1687 - Crate-level documentation
- Elegant Library APIs in Rust
- Rust Design Patterns