この記事はEAGLYS Advent Calendar 2021の2日目の記事です。
モチベーション
タイトルの2つの違いがどうにも分かりづらかったので、自分なりに解説します。
https://doc.rust-jp.rs/rust-by-example-ja/generics/assoc_items.html
簡単なまとめ
実装できる回数 | 型パラメタ | |
---|---|---|
ジェネリクス | 1回以上 | 必要 |
関連型 | 1回 | 不要 |
関連型
まず、 IntPack
に Pack
トレイトを実装して標準出力します。関連型を使った場合は以下です。
use std::fmt::Debug;
#[derive(Debug)]
struct IntPack(i32);
trait Pack {
type A;
fn get(&self)->Self::A;
fn set(&mut self,content:Self::A)->();
}
impl Pack for IntPack {
type A = i32;
fn get(&self) -> Self::A {
self.0
}
fn set(&mut self, content: Self::A)->(){
self.0 = content;
}
}
fn debug<A:Pack+Debug>(a:A){
dbg!(a);
}
fn main() {
let int_pack = IntPack(10);
debug(int_pack);
}
ジェネリクスを使った場合はIntPackのi32
にしか実装しないことがわかっているのに、型パラメタを指定しなければなりません。ジェネリクスの場合、複数のPack<A>
の実装がコードに存在しうるからです。
use std::fmt::Debug;
#[derive(Debug)]
struct IntPack(i32);
trait Pack<A> {
fn get(&self)->A;
fn set(&mut self,content:A)->();
}
impl Pack<i32> for IntPack {
fn get(&self) -> i32 {
self.0
}
fn set(&mut self, content: i32)->(){
self.0 = content;
}
}
fn debug<A:Pack<i32>+Debug>(a:A){
dbg!(a);
}
fn main() {
let int_pack = IntPack(10);
debug(int_pack);
}
型A
についてだけPack
トレイトが実装されることがわかっている場合、関連型は有用です。
ジェネリクスの強みは?
関連型の場合、一回しかPack
トレイトを実装できません。これは関連型というよりは、トレイトの仕様です。複数実装すると impl Pack for IntPack {~}
が衝突します。
use std::fmt::Debug;
#[derive(Debug)]
struct IntPack(i32);
trait Pack {
type A;
fn get(&self)->Self::A;
fn set(&mut self,content:Self::A)->();
}
impl Pack for IntPack {
type A = i32;
fn get(&self) -> Self::A {
self.0
}
fn set(&mut self, content: Self::A)->(){
self.0 = content;
}
}
// conflicting implementations of trait `Pack` for type `IntPack`
impl Pack for IntPack {
type A = i64;
fn get(&self) -> Self::A {
self.0
}
fn set(&mut self, content: Self::A)->(){
self.0 = content;
}
}
fn debug<A:Pack+Debug>(a:A){
dbg!(a);
}
fn main() {
let int_pack = IntPack(10);
debug(int_pack);
}
ジェネリクスの場合、複数回実装が可能です。impl Pack<A> for IntPack {~}
のAが異なれば、それは異なる実装になるので、実装が衝突することはありません。
use std::fmt::Debug;
#[derive(Debug)]
struct IntPack(i32);
trait Pack<A> {
fn get(&self)->A;
fn set(&mut self,content:A)->();
}
impl Pack<i32> for IntPack {
fn get(&self) -> i32 {
self.0
}
fn set(&mut self, content: i32)->(){
self.0 = content;
}
}
impl Pack<i64> for IntPack {
fn get(&self) -> i64 {
self.0 as i64
}
fn set(&mut self, content: i64)->(){
self.0 = content as i32;
}
}
fn debug<A:Pack<i32>+Debug>(a:A){
dbg!(a);
}
fn debug64<A:Pack<i64>+Debug>(a:A){
dbg!(a);
}
fn main() {
let int_pack = IntPack(10);
debug(int_pack);
}
まとめ
関連型とジェネリクスの使い分けは
- 複数回実装可能だが、型パラメータを指定しないといけないのがジェネリクス
- 一回しか実装できないが、型パラメータを指定しなくてよいのが関連型
と言えます。