⚙️
[Rust] 関連型の用途とは
はじめに
以下を読んでいて、いまいちピンと来なかったので、実際に書きながら使い分けを調べました。
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