予測性

スマートポインタがinherentメソッドを持っていない (C-SMART-PTR)

例えば、Box::into_rawは次のように定義されています。

#![allow(unused)]
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)]
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]より特徴的です。

変換メソッドは、関係する型の中で、より特徴的なものが持つべきです。 従って、stras_bytesメソッド及びfrom_utf8コンストラクタを持つのです。 この方が直感的であるだけでなく、&[u8]のような型が無数の変換メソッドで汚染されていくという事態が避けられます。

明確なレシーバを持つ関数がメソッドになっている (C-METHOD)

特定の型と強く関連した操作についてはメソッドにしてください。

#![allow(unused)]
fn main() {
impl Foo {
    pub fn frob(&self, w: widget) { /* ... */ }
}
}

関数にしてはいけません。

#![allow(unused)]
fn main() {
pub fn frob(foo: &Foo, w: widget) { /* ... */ }
}

関数でなくメソッドを選ぶことには多数の利点があります。

  • インポートしたり関数へのパスを記述したりする必要がない。その型の値さえあれば必要な操作ができます。
  • 呼び出し時に自動借用が働きます。 (可変借用も含めて)
  • 「この型Tで何ができるんだろう」という疑問への答えが簡単になります。 (特にrustdocを使用している場合)
  • self記法が使われるため、より簡潔かつ明白に所有権の区別が示されます。

関数がoutパラメータを持たない (C-NO-OUT)

例えば複数のBarを返すときはこのようにしてください。

#![allow(unused)]
fn main() {
fn foo() -> (Bar, Bar)
}

このようにoutパラメータのようなものを取ってはいけません。

#![allow(unused)]
fn main() {
fn foo(output: &mut Bar) -> Bar
}

タプルや構造体を使って複数の値を返しても効率のよいコードにコンパイルされますし、 ヒープの確保も行われません。複数の値を返す必要があるならこれらの型を利用すべきです。

例外は関数が呼び出し側の所有するデータを変更する場合です。 例えば、バッファの再利用をする場合は次のようになるでしょう。

#![allow(unused)]
fn main() {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>
}

奇妙な演算子オーバーロードを行っていない (C-OVERLOAD)

組み込みの演算子(*|など)はstd::opsにあるトレイトを実装することで使えるようになります。 これらの演算子には元から意味が付与されています。 例えば、Mulは乗算のような(そして結合性などの特性を共有した)演算にのみ実装されるべきです。

DerefDerefMutを実装しているのはスマートポインタだけである (C-DEREF)

Derefトレイトはコンパイラによって様々な状況で暗黙的に使われ、メソッドの解決と関わります。 その周辺の規則はスマートポインタを念頭において設計されているため、 これらのトレイトはスマートポインタに対してのみ実装されるべきです。

標準ライブラリでの例

コンストラクタはスタティックなinherentメソッドである (C-CTOR)

Rustにおいて、「コンストラクタ」は単なる慣習に過ぎません。 コンストラクタの命名には様々な慣習があり、その区別が分かり辛いことが多々あります。

最も基本的なコンストラクタの形は引数のないnewメソッドです。

#![allow(unused)]
fn main() {
impl<T> Example<T> {
    pub fn new() -> Example<T> { /* ... */ }
}
}

コンストラクタは、その生成する型のスタティック(selfを取らない)なinherentメソッドです。 型をインポートする慣習と併せれば、分かりやすく簡潔にその型を生成することができます。

#![allow(unused)]
fn main() {
use example::Example;

// Construct a new Example.
let ex = Example::new();
}

The name new should generally be used for the primary method of instantiating a type. Sometimes it takes no arguments, as in the examples above. Sometimes it does take arguments, like Box::new which is passed the value to place in the Box.

主にI/Oリソースを表す型では、File::openMmap::openTcpStream::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_beString::from_utf8のように入力の型が単なるデータ列であるとき、 コンストラクタの名前によってその意味を伝えることが可能です。

標準ライブラリでの例