‼️
【Rust】ジェネリクスを使いこなして型の柔軟性を手に入れろ!
Rustのジェネリクスは、型安全性を保ちながらコードの再利用性を高めるために利用される機能です。ジェネリクスを使うことで、同じロジックを異なる型に対して適用することが可能になります。
この記事ではジェネリクスの基本的な使い方について解説します。
「The Rust Programming Language」を読んでもしっくり来ていないあなた、確実にこの記事で理解し、使いこなせるようになります。
ジェネリクスとは
ジェネリクスとは、具体的な型を指定せずに抽象的に型を表現する方法です。これにより、特定の型に依存しない柔軟なコードを書くことができます。
例えば、2つの数値を足し算する場合を考えてみましょう。
ジェネリクスを使わない場合
fn add_i32(a: i32, b: i32) -> i32 {
a + b
}
fn add_f64(a: f64, b: f64) -> f64 {
a + b
}
足し算する関数だけど、型が i32
と f64
で違うから別の関数を用意したけど一緒にしたい。こういう時にジェネリクスを使うと型を抽象化して1つの関数で済ませることができます。
ジェネリクスを使う場合
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
これで、i32
でも f64
でも、1つの関数で処理できるようになりました。
ジェネリクスの構文を理解する
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T
-
add<T>
- ジェネリクス関数であることを示しています。
-
T: std::ops::Add
- 「型Tが加算演算子
+
を使えること」を制約として示しています。
- 「型Tが加算演算子
-
<Output = T>
- 加算の結果の型をTと同じ型に指定しています。
-
(a: T, b: T)
- 関数の引数をジェネリックな型 T で定義しています。
-
-> T
- 戻り値の型も、引数と同じ抽象的な型 T として指定しています。
あらゆる構文でジェネリクスを使用する
1.関数
fn identity<T>(x: T) -> T {
x
}
2. 構造体
struct Point<T> {
x: T,
y: T,
}
let p1 = Point { x: 10, y: 20 };
let p2 = Point { x: 1.2, y: 3.4 };
3. 列挙型
enum Result<T, E> {
OK(T),
Err(E),
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Result::Err("Cannot divide by zero.".to_string())
} else {
Result::Ok(a / b)
}
}
4. トレイト
trait Summary<T> {
fn summarize(&self) -> T;
}
struct Book {
title: String,
}
impl Summary<String> for Book {
fn summarize(&self) -> String {
format!("Title: {}", self.title)
}
}
まとめ
- ジェネリクスとを使うとコードの再利用性が向上する
- 柔軟で抽象的な設計が可能になる
- どんな構文でも利用可能
Discussion