Closed18

Tour of Rust's Standard Library Traits メモ

yuma14yuma14

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;
yuma14yuma14

Auto Traits

より詳細な情報はauto_traits - The Rust Unstable Bookを参照。

Auto Traitは、明示的な実装も否定実装も無く、型のメンバーが条件を満たすとき自動で実装される。

否定実装

こういうやつ↓

impl !Sync for Hoge
yuma14yuma14

Relaxed Bound

すべてのジェネリック型は暗黙のうちにSizedトレイト境界を持つ。つまり、

fn func<T>(t: &T) {}

などと書いたとき、実は暗黙のうちに以下のようなトレイト境界が設定されている。

fn func<T: Sized>(t: &T) {}

TT: Sizedに限定したくないとき、?Sizedを使って以下のように書く。

fn func<T: ?Sized>(t: &T) {}

このとき?Sizedをrelaxed boundと呼ぶ。relaxed boundは現在のところSizedトレイト専用の機能である。

yuma14yuma14

Copy

Copyトレイトを実装してよいのはコピー操作が単純なビット単位のコピーである場合のみ。
Copyトレイトは自分で実装することはできず、コンパイラによって実装される。

yuma14yuma14

Any

AnyはすべてのT: 'static + ?Sizedについてブランケット実装されている。そのためdyn Anyを使うと動的型付けのようなことができる。ダウンキャストするときはAny::downcast_ref::<T>(& self) -> Option<&T>またはAny::downcast_mut::<T>(&mut self) -> Option<&mut T>を使う。

yuma14yuma14

DisplayとToString

ToStringto_stringメソッドを提供する。Displayを実装していればToStringも実装される。

impl<T: Display + ?Sized> ToString for T;
yuma14yuma14

フォーマット用のトレイト

format!マクロ等のプレースホルダの種類によって対応するトレイトが決まっている。

トレイト プレースホルダ 説明
Display {} 表示用の表現
Debug {:?} デバッグ用の表現
Octal {:o} 8進数の表現
LowerHex {:x} 小文字の16進数の表現
UpperHex {:X} 大文字の16進数の表現
Pointer {:p} メモリアドレスとしての表現
Binary {:b} 2進数の表現
LowerExp {:e} 小文字の指数表現
UpperExp {:E} 大文字の指数表現
yuma14yuma14

PartialEq

PartialEqを実装するなら、すべてのa, b, cについて対称律a == bb == aと推移律a == b && b == ca == 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 < ca < cが満たされるようにしなければならない。

Ord

Ordを実装するなら、すべてのa, bについてa < b, a == b, a > bのいずれか1つだけが成立するようにしなければならない。

yuma14yuma14

Fn, FnMut, FnOnce

手動で実装することはできない。

Fn: FnMutFnMut: FnOnceという関係がある。

yuma14yuma14

Deref, DerefMut

newtypeパターンで中身の型のメソッドを透過的に呼び出せるようにするために使われることがある。しかし、本来はスマートポインタを実現するためのトレイトなのでそれ以外の用途に使用するのは推奨されない。かわりにAsRef, AsMutを使うべきである。

yuma14yuma14

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]と書く必要があるはずだが、糖衣構文により*が挿入されるので実際は必要ない。
yuma14yuma14

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)
    }
}
yuma14yuma14

FromStrとTryFrom

FromStrTryFrom<&str>は役割が被っている(メソッド名は異なる)。FromStrより後にTryFromが追加されたという経緯があるらしい。

yuma14yuma14

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はオブジェクト指向ではないため、ModeratorUserを継承できない。かわりにModeratorAsRef<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);
}
yuma14yuma14

Borrow, BorrowMut

Borrow<T>AsRef<T>に似ているが、より制約が強い。&TEqHashOrdの実装がSelfと同じものであることが保証される。もともとは、HashSetHashMap等でStringのキーを検索するときに代わりに&strを使いたいという限定的な問題に対処するために用意された。ブランケット実装もあるため、通常は自分で実装する必要はない。

yuma14yuma14

ToOwned

ToOwnedCloneを一般化したものである。Clone&TTにすることができるが、ToOwned&BorrowedOwnedにすることができる(where Owned: Borrow<Borrowed>)。

例えば、Clone&StringStringに、&PathPathにすることができるが、ToOwnedはこれに加えて&strStringに、&PathPathBufにすることができる。String: Borrow<str>PathBuf: Borrow<Path>という関係があるからである。

通常は自分で実装する必要はない。

yuma14yuma14

FromIterator

trait Iterator {
  type Item;
  // ...
  fn collect<B>(self) -> B where B: FromIterator<Self::Item>;
  // ...
}

主にIteratorトレイトのcollectメソッドの戻り値に使う。

このスクラップは2ヶ月前にクローズされました