⚙️

[Rust] 関連型の用途とは

2023/02/28に公開

はじめに

以下を読んでいて、いまいちピンと来なかったので、実際に書きながら使い分けを調べました。
https://doc.rust-jp.rs/book-ja/ch19-03-advanced-traits.html

Associated Type

  • 1つの型に対して、1つのトレイトのみ実装できる
  • コンパイラが実装を見つけるため、利用する際に指定不要
    • vec!["1", "2", "3"].first()
    • vec![1, 2, 3].first()
Associated Type
trait Extractor {
    type Output;
    fn first(&self) -> Self::Output;
}

// Vec<&str>に対して1つの実装
impl Extractor for Vec<&str> {
    type Output = String;
    fn first(&self) -> Self::Output {
        self[0].to_string()
    }
}

// Vec<u32>に対して1つの実装
impl Extractor for Vec<u32> {
    type Output = String;
    fn first(&self) -> Self::Output {
        self[0].to_string()
    }
}

// Outputだけ変えようとしてもエラーとなる
// conflicting implementations of trait `Extractor` for type `Vec<u32>`
impl Extractor for Vec<u32> {
    type Output = u32;
    fn first(&self) -> Self::Output {
        self[0]
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test() {
        assert_eq!(vec!["1", "2", "3"].first(), "1");
        assert_eq!(vec![1, 2, 3].first(), "1");
    }
}

Generics

  • 1つの型に対して、複数のトレイトを実装できる
  • 同じ型に対して、複数のトレイトが存在する場合、どれを使うか指定必須
    • Extractor::<u32>::first(&arr)
    • Extractor::<String>::first(&arr)
    • etc...
Generics
trait Extractor<T> {
    fn first(&self) -> T;
}

impl Extractor<String> for Vec<&str> {
    fn first(&self) -> String {
        self[0].to_string()
    }
}

impl Extractor<String> for Vec<u32> {
    fn first(&self) -> String {
        self[0].to_string()
    }
}

impl Extractor<u32> for Vec<u32> {
    fn first(&self) -> u32 {
        self[0]
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test() {
        // 実装が1つなら推論される
        assert_eq!(vec!["1", "2", "3"].first(), "1");
        // 実装が複数なら指定必須
        assert_eq!(Extractor::<String>::first(&vec![1, 2, 3]), "1");
        assert_eq!(Extractor::<u32>::first(&vec![1, 2, 3]), 1);
    }
}

まとめ

  • 型とトレイトの関連を見て使う方式を決める
  • 基本は関連型で良さそう
  • 将来的に1:Nの関係になりそうなら、ジェネリクスが良さそう

Discussion