Rustコトハジメ

プログラミング言語Rustに関する情報をお届けします。

トレイトの型を見れば何をするのかがわかる

私が混乱したこと

Rustを勉強し始めた当時、私を混乱させたのは、

impl XXX for [T]

のような書き方でした。

[T]というのは直接的には操作出来ないものであって、操作するためには参照をとらなければいけないものです。したがって、

impl XXX for &[T]

の方が「操作可能な型に対して何か定義している」という点で意味があるのではないかと思い、[T]に対して実装するという書き方に混乱したものです。

所有権や参照のコントロールはトレイトの責務

Rustは、&&mutという参照も型の一部という言語です。そのため、トレイトの中で「この関数は&mutが必要だけど、これは&しか必要ないな」「これは所有権が必要」というコントロールをしたいのです。だから、ほとんどの場合、参照のついた型に対して実装することがないのです。それは、トレイトの定義側の責任だからです。コンパイラがやっていることはfor Tの部分のTをSelf型として、トレイト内の定義に当てはめるだけです。

例えば、Indexというトレイトを考えると、

pub trait Index<Idx: ?Sized> {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

その実装の一つであるimpl<T, I> Index<I> for [T]というのは、Selfを[T]として、

fn index(&[T], index: I) -> &Self::Output

という型をもつ関数を実装せよということになります。この関数のように、引数が参照ならば返り値も参照であるべき場合には、トレイト内で参照をつける方が都合がいいですよね。

トレイトの定義を見れば、それが何をしたいのかわかる

この事実を利用すると、トレイト自体の定義を読むだけでそのトレイトが何をしたいのかを理解することが出来ます。例えば、

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

Derefトレイトは参照を参照に変換するために存在することがわかりますし、

pub trait Into<T>: Sized {
    /// Performs the conversion.
    #[stable(feature = "rust1", since = "1.0.0")]
    fn into(self) -> T;
}

Intoトレイトは所有権をとって所有権を返すということがわかります。

例えば、Intoトレイトで、impl Into<&Target> for &Tなどと書いたら、Derefトレイトと同じではないのかと思ったのが私が混乱したきっかけですが、深く考えずに素直に読めばいいだけなのです。

例外ケース

所有権や参照の扱いはトレイト側で担保してくれるので、ほとんどのケースでは「型そのもの」について定義を書いていくのが正解となるのですが、参照に対して実装している場合も稀にあります。

impl<T: ?Sized> Deref for &T
impl<T: ?Sized> Deref for &mut T

これは、&&T&Tに変換するための実装ですが、このような書き方をするのは明らかに特殊で、通常は、型Tに対して実装をして、参照は関数の方でつけているのです。