Tour of Rust's Standard Library Traits メモ
Tour of Rust's Standard Library Traitsを読んだ。Rustの標準ライブラリのトレイトについて解説した記事である。
以下、メモ
Associated TypeとGeneric Parameters
The general rule-of-thumb is:
- Use associated types when there should only be a single impl of the trait per type.
- Use generic types when there can be many possible impls of the trait per type.
それぞれの型について、あるトレイトの実装が1つだけである必要があるなら関連型を使う
それぞれの型について、あるトレイトの実装が複数あってもいいならジェネリクスを使う
trait TraitA {
type Type;
}
struct Struct;
impl TraitA for Struct {
type Type = i32;
}
// ❌ コンパイルエラー。StructについてTraitAの実装は1つだけしか定義できない
impl TraitA for Struct {
type Type = u32;
}
trait TraitG<T> {}
struct Struct;
impl TraitG<i32> for Struct;
// ✅ コンパイルできる。
impl TraitG<u32> for Struct;
Auto Traits
より詳細な情報はauto_traits - The Rust Unstable Bookを参照。
Auto Traitは、明示的な実装も否定実装も無く、型のメンバーが条件を満たすとき自動で実装される。
否定実装
こういうやつ↓
impl !Sync for Hoge
Relaxed Bound
すべてのジェネリック型は暗黙のうちにSized
トレイト境界を持つ。つまり、
fn func<T>(t: &T) {}
などと書いたとき、実は暗黙のうちに以下のようなトレイト境界が設定されている。
fn func<T: Sized>(t: &T) {}
T
をT: Sized
に限定したくないとき、?Sized
を使って以下のように書く。
fn func<T: ?Sized>(t: &T) {}
このとき?Sized
をrelaxed boundと呼ぶ。relaxed boundは現在のところSized
トレイト専用の機能である。
Copy
Copy
トレイトを実装してよいのはコピー操作が単純なビット単位のコピーである場合のみ。
Copy
トレイトは自分で実装することはできず、コンパイラによって実装される。
Any
Any
はすべてのT: 'static + ?Sized
についてブランケット実装されている。そのためdyn Any
を使うと動的型付けのようなことができる。ダウンキャストするときはAny::downcast_ref::<T>(& self) -> Option<&T>
またはAny::downcast_mut::<T>(&mut self) -> Option<&mut T>
を使う。
DisplayとToString
ToString
はto_string
メソッドを提供する。Displayを実装していればToStringも実装される。
impl<T: Display + ?Sized> ToString for T;
フォーマット用のトレイト
format!
マクロ等のプレースホルダの種類によって対応するトレイトが決まっている。
トレイト | プレースホルダ | 説明 |
---|---|---|
Display |
{} |
表示用の表現 |
Debug |
{:?} |
デバッグ用の表現 |
Octal |
{:o} |
8進数の表現 |
LowerHex |
{:x} |
小文字の16進数の表現 |
UpperHex |
{:X} |
大文字の16進数の表現 |
Pointer |
{:p} |
メモリアドレスとしての表現 |
Binary |
{:b} |
2進数の表現 |
LowerExp |
{:e} |
小文字の指数表現 |
UpperExp |
{:E} |
大文字の指数表現 |
PartialEq
PartialEqを実装するなら、すべてのa
, b
, c
について対称律a == b
⇒b == a
と推移律a == b && b == c
⇒a == c
が満たされるようにしなければならない。
Eq
Eqを実装するなら、すべてのa
が反射律a == a
を満たすようにしなければならない。
Hash
EqとHashを実装するなら、すべてのa
, b
についてa == b
ならばa.hash() == b.hash()
となるようにしなければならない。
そのため、EqとHashは両方deriveマクロで実装するか両方手動で実装するかのどちらかにしたほうが良い。
PartialOrd
PartialOrdを実装するなら、すべてのa
, b
, c
について非対称律a < b
⇒!(a > b)
と推移律a < b && b < c
⇒a < c
が満たされるようにしなければならない。
Ord
Ordを実装するなら、すべてのa
, b
についてa < b
, a == b
, a > b
のいずれか1つだけが成立するようにしなければならない。
Fn, FnMut, FnOnce
手動で実装することはできない。
Fn: FnMut
、FnMut: FnOnce
という関係がある。
Deref, DerefMut
newtypeパターンで中身の型のメソッドを透過的に呼び出せるようにするために使われることがある。しかし、本来はスマートポインタを実現するためのトレイトなのでそれ以外の用途に使用するのは推奨されない。かわりにAsRef, AsMutを使うべきである。
Index, IndexMut
trait Index<Idx: ?Sized> {
type Output: ?Sized;
fn index(&self, index: Idx) -> &Self::Output;
}
let v: Vec<i32> = vec![1, 2, 3];
let num = v[0];
let num = *v[0]; // ❌
/// index()の戻り値の型は&i32なので*v[0]と書く必要があるはずだが、糖衣構文により*が挿入されるので実際は必要ない。
From, Into
Fromを実装すればIntoはブランケット実装される。
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
TryFrom, TryInto
TryFromを実装すればTryIntoはブランケット実装される。
impl<T, U> TryInto<U> for T
where
U: TryFrom<T>,
{
type Error = U::Error;
fn try_into(self) -> Result<U, U::Error> {
U::try_from(self)
}
}
FromStrとTryFrom
FromStr
とTryFrom<&str>
は役割が被っている(メソッド名は異なる)。FromStr
より後にTryFrom
が追加されたという経緯があるらしい。
AsRef, AsMut
AsRef自体は単なる参照から参照への変換である。
よくある使い方としては、関数が所有権を取っても取らなくてもいいようにできる。
/// この関数は&str, &String, Stringのどれでも受け取れる
fn func(s: impl AsRef<str>) {}
もう1つのよくある使い方は、2つの型AとBがA is a Bの関係にあるとき、AをBであるかのように扱うために使う。例えば下の例ではModerator is a Userという関係があるが、Rustはオブジェクト指向ではないため、Moderator
はUser
を継承できない。かわりにModerator
はAsRef<User>
を実装するので、impl AsRef<User>
を受け取る関数は&User
も&Moderator
も受け取ることができる。
struct User {
name: String,
age: u32
}
struct Moderator {
user: User,
privilege_flag: u32
}
impl AsRef<User> for User {
fn as_ref(&self) -> &User {
self
}
}
impl AsRef<User> for Moderator {
fn as_ref(&self) -> &User {
&self.user
}
}
fn create_post(u: impl AsRef<User>) {
let user = u.as_ref(); // user: &User
// userを使う処理
// ...
}
fn main() {
let user = User { /* 略 */ };
let moderator = Moderator { /* 略 */ };
create_post(&user);
create_post(&moderator);
}
Borrow, BorrowMut
Borrow<T>
はAsRef<T>
に似ているが、より制約が強い。&T
のEq
、Hash
、Ord
の実装がSelf
と同じものであることが保証される。もともとは、HashSet
、HashMap
等でString
のキーを検索するときに代わりに&str
を使いたいという限定的な問題に対処するために用意された。ブランケット実装もあるため、通常は自分で実装する必要はない。
ToOwned
ToOwned
はClone
を一般化したものである。Clone
は&T
をT
にすることができるが、ToOwned
は&Borrowed
をOwned
にすることができる(where Owned: Borrow<Borrowed>
)。
例えば、Clone
は&String
をString
に、&Path
をPath
にすることができるが、ToOwned
はこれに加えて&str
をString
に、&Path
をPathBuf
にすることができる。String: Borrow<str>
、PathBuf: Borrow<Path>
という関係があるからである。
通常は自分で実装する必要はない。
FromIterator
trait Iterator {
type Item;
// ...
fn collect<B>(self) -> B where B: FromIterator<Self::Item>;
// ...
}
主にIteratorトレイトのcollectメソッドの戻り値に使う。