この記事で書くこと
- ジェネリクスとは
- ジェネリクスの書き方
- ジェネリクスがあることの嬉しさ
ジェネリクスとは
genericsの意味を調べてみると、以下のようなものでした。
- generics:一般的な
つまり、ジェネリクスを使用することでより、「一般的な」処理を書くことができます。何に対して「一般的」にするのかというと、型に対してです。
ジェネリクスの書き方
ジェネリクスは型に対して、一般性を持たせることができます。ジェネリクスは、構造体、enum、関数などで使用できます。ここでは、構造体で使用することにします。例えば、構造体の要素に対して、一般性を持たせるには以下のように書きます。
struct Hoge<T> {
hoge_elm: T,
}
fn main() {
let a = Hoge { hoge_elm: 10 };
let b = Hoge { hoge_elm: "Hoge".to_string() };
let fuga = Fuga { fuga_elm: 100 };
let c = Hoge { hoge_elm: fuga };
println!("{}", a.hoge_elm);
println!("{}", b.hoge_elm);
println!("{}", c.hoge_elm.fuga_elm);
}
struct Fuga {
fuga_elm: usize,
}
実行結果
10
Hoge
コード解説
構造体定義の際、Tをジェネリックな変数として定義します。そしてhの要素をT型します。このTには、任意の型を設定することができます。上記の例では、aでは整数型のデフォルトであるi32、bではString、cでは自分で作った構造体のFugaが Tの型として指定されます。
ジェネリクスの嬉しさ
ジェネリクスの嬉しさは、型に一般性を持たせることで、様々な型に対応した処理を記述できることです。上記のコードの例でいくと、ジェネリクスを使わない場合、以下のようなコードになります。
struct Hoge_i32 {
hoge_elm: i32,
}
struct Hoge_String {
hoge_elm: String,
}
struct Hoge_Fuga {
hoge_elm: Fuga,
}
fn main() {
let a = Hoge_i32 { hoge_elm: 10 };
let b = Hoge_String { hoge_elm: "Hoge".to_string() };
let fuga = Fuga { fuga_elm: 100 };
let c = Hoge_Fuga { hoge_elm: fuga };
println!("{}", a.hoge_elm);
println!("{}", b.hoge_elm);
println!("{}", c.hoge_elm.fuga_elm);
}
struct Fuga {
fuga_elm: usize,
}
処理は同じなのに型によって、別の構造体を作成しなければなりません。また、同じ名前の構造体は許されないので、型名を付与する等の処置をしなければなりません。ジェネリクスを使うと、このような「処理は同じだが複数の型に適用したい」といった場合に記述量を少なく書くことができます。また、ユーザーが作成した新たな型にも対応できます。
ジェネリクスの挙動
ジェネリクスを用いた処理は、実際に先ほど書いたコードのように、それぞれの型に展開されます。
以下のジェネリクスを使った構造体定義について考えてみます。
struct Hoge<T> {
hoge_elm: T,
}
この構造体は、コードの中でi32とStringで使われると、以下のようなコードへとコンパイルされます。ちなみに、このコードはコンパイル時に生成されるものなので、ソースファイルに書いてもコンパイルできません。コードの中で、どの型を使用しているのかは、コンパイラが判断してくれます。
struct Hoge<i32> {
hoge_elm: i32,
}
struct Hoge<String> {
hoge_elm: String,
}
コンパイル後、ジェネリクスがそれぞれの型に展開されている例として、i32型だけに適用されるメソッドを書いてみます。
struct Hoge<T> {
hoge_elm: T,
}
impl<T> Hoge<T> {
fn ya(&self) {
println!("ya");
}
}
impl Hoge<i32> {
fn hi(&self) {
println!("hi");
}
}
fn main() {
let a = Hoge { hoge_elm: 10 };
let b = Hoge { hoge_elm: "Hoge".to_string() };
a.ya();
a.hi();
b.ya();
//b.hi(); // not allowed
}
こちらのコードでは、どんな型でも実装されるyaメソッドと、i32型にのみ実装されるhiメソッドを書いています。任意の型に対してメソッドを実装するHoge<T>に対してメソッドを記述します。i32型のみに実装したいメッソどはHoge<i32>に対してメソッドを記述します。
hiメソッドがi32の型のみに実装されるのは、ジェネリクスがコンパイル時に、i32型とString型に書き分けられるからです。その書き分けられた型の、i32の方にのみhiメソッドを実装できるのです。
最後に
ここではジェネリクスの基礎について記載しました。この記事ではジェネリクスを使って構造体を定義しました。ジェネリクスは、構造体だけでなく、関数やトレイトでも使われます。関数で使う際には、トレイト境界についての知識が必要になってきます。
コメント