ジェネリック型が必要になるシーン
ジェネリック型は、構造体の型定義や関数のシグネチャを書くときに必要になる型について、呼び出し側でその型を決定できるようにする仕組みです。
関数にジェネリック型を使う
関数のシグネチャでは引数と戻り値の型を明記します。ただし、関数の目的によっては、特定の型に限らず、一定の条件を満たしている型であれば、関数の宣言時に型を限定する必要がないものもあります。
その場合、ジェネリック型の使用が適しています。ジェネリック型は関数名の後に<型引数名>
と書くことで宣言します。一般的に型引数はT
で宣言します(決まりではなく慣習です)。
ジェネリック型を定義すると、そのシグネチャ内でそのジェネリック型を使うことができます。例えば、何かしらの型の配列から特定の要素を返す関数であれば、引数の型に&[T]
、戻り値の型にT
と書くことができます。
fn first_element<T>(list: &[T]) -> &T {
&list[0]
}
fn main() {
let numbers = vec![10, 20, 30];
let chars = vec!['a', 'b', 'c'];
println!("The first number is {}", first_element(&numbers));
println!("The first char is {}", first_element(&chars));
}
関数によっては、ジェネリック型として渡される型に条件を設けたい場合があります(例:二要素間で等価比較が可能である、標準出力が可能である、など)。それはトレイト(後日紹介)という仕組みで実現します。
構造体にジェネリック型を使う
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.0, y: 4.0 };
println!("integer_point: ({}, {})", integer_point.x, integer_point.y);
println!("float_point: ({}, {})", float_point.x, float_point.y);
}
列挙型にジェネリック型を使う
enum Option<T> {
Some(T),
None,
}
fn main() {
let some_number = Option::Some(5);
let some_string = Option::Some("Hello");
println!("{:?}, {:?}", some_number, some_string);
}
メソッドにジェネリック型を使う
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let point = Point { x: 5, y: 10 };
println!("point.x: {}", point.x());
}
ジェネリック型がもたらす効能
ジェネリック型を使うと、宣言を柔軟に行えるため、関数の呼び出し時や構造体の宣言時を柔軟に行いつつも、型安全性の恩恵を受けることができます。
例えば、以下の例では引数に応じて戻り値の型が自動的に決定されます。
fn identity<T>(value: T) -> T {
value
}
fn main() {
let number = identity(42);
let text = identity("Hello");
println!("number: {}, text: {}", number, text);
}
また、構造体の複数のフィールドの型をT
で統一していた場合、インスタンスを生成するときにそれらのフィールドの型が一致していないとコンパイルエラーになります。
struct Pair<T> {
first: T,
second: T,
}
fn main() {
// これはコンパイルが通る
let pair = Pair { first: 1, second: 2 };
// 型が一致しない場合はコンパイルエラー
// let invalid_pair = Pair { first: 1, second: "string" };
}
このように、ジェネリック型を使うと、柔軟性と型安全性を両立させることができます。
この機構は他の言語でも積極的に採用されている印象です。TypeScriptとほぼ同等の書き心地であり、使いやすいのではないでしょうか。
TypeScriptでは、ジェネリック型に条件を付けたい場合はextends
を使います。Rustにも似たようなことを実現するためのトレイトという機能があるので、次回はその解説を予定しています。