読者です 読者をやめる 読者になる 読者になる

テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(rust report) Rustの変換系traitをまとめる

Rustには所有権がある. 所有権は静的な型で表現されており, コードに対する揺るぎない保証をプログラマに与えてくれる. しかし一方で, それがRustの学習コストが高いと言われる所以でもある.

この記事では, Rustで代表的に使われる変換系traitをまとめる. 間違っていたら教えてほしい.

  • std::convert
  • std::borrow
  • std::ops::Deref

これらの多くは, 出来るだけGeneralizedされたコードを書くために用意されている. (従って必須ではない)

Deref以外

このうちDeref以外は型を変換したり, referenceを抽象化したりするために存在する. きっと世界初になると思うが, これらの関係を図にまとめた

f:id:akiradeveloper529:20161104223014j:plain

ざっくり説明する

  • AsRefとBorrowはともに, 「&selfを&Tに変換し得るもの」という意味である. (AsMut, BorrowMutはmut版) この2つのtraitは, 実は同じ型である. これには歴史的な経緯があり, どちらかに統一しようぜという声はあるもののまだ実行されていない. (詳しくはhttp://qiita.com/hibariya/items/b24f893f88d0dc931c61)
  • ToOwnedは「&selfをTに変換し得るもの」という意味である. これはつまり, cloneであるが, cloneより抽象度が高い (Cloneは出力がSelfに限定されているため)
  • From, Intoはownershipをとって別の型に変換するためのもの. impl<T, U> Into<U> for T where U: From<T> というimplによって互いに行き来することが出来る. これは「もし引数がreferenceだったらcloneしてTのownershipを得るし, そもそもmoveされてきた場合はそのまま使い続ける(identity関数)」用途で使う
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T where U: From<T> {
    fn into(self) -> U {
        U::from(self)
    }
}
  • CoWenumであり, 「初期にはreferenceだが, 書き込みが必要ならばcloneしてownershipをとる」という実装である. CoWはCopy-On-Writeの意味.

Deref

Derefはreferenceをdereferenceするためのものである

で終了出来る人は良いのだが, Rustを学習しようというプログラミング野郎ならば以下の2点を疑問に思うはずだ. 思わないならば, よっぽど頭が良いか, Goでも書いてるのがお似合いということだ.

  1. auto-dereferencingはなぜ出来るのか
  2. なぜderefは&Tを返すのか?Tではないのか?

1. auto-dereferencingとは, Rustコンパイラが型が合うまで*を入れてdereferenceしてくれることと説明されている. ("because the compiler will insert as many * operations as necessary to get it right." https://doc.rust-lang.org/book/deref-coercions.html) この魔法によると, &&&&&Tは自動的に&Tになったりする. (余談だが, Rustはmethodに限って自動borrowを行うが, この逆のような話) 種明かしは以下の実装. これによって型が合うまで&を削っていくことができる. (つまり, Rustコンパイラ自体の機能ではない)

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T: ?Sized> Deref for &'a T {
    type Target = T;

    fn deref(&self) -> &T { *self }
}

2. なぜderefの返り値は&Tなのかについては, stackoverflowで質問をしている人がいる

stackoverflow.com

おれなり理解は,

  • メモリ領域をがめているlvalueを返すことはつまりownershipをmoveすることになる. (Copyの場合は違うが) これはまじで何やってるのかわからないからreferenceを返していくしかない
  • &TからTの値を読む方法はコンパイラが知っている. *はsyntax sugarであり, コンパイラに対して&Tを渡したらあとはうまいことTにしてくれる.

まぁぶっちゃけよくわからないが, Derefについては利用上は誤ることは一切ないので実用上は問題ない.

まとめ

Rustは素晴らしいと思うので, 今後もやっていく