ジェネリック関数
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 27/61。
ジェネリクスは構造体だけに限定されません。任意の型で動作するスタンドアロン関数を記述することもできます。これは、特定の構造体に属さないものの、柔軟性が必要なユーティリティ関数が必要な場合に便利です。
構文は、構造体で見たものと同様です。関数名の後のアングルブラケット(山括弧)内にジェネリックパラメータを宣言し、それをパラメータと戻り値の型で使用します。
fn identity<T>(value: T) -> T {
value
}
この identity 関数は、あらゆる型の値を受け取り、それをそのまま返します。<T> はジェネリックを宣言し、value: T はそれを引数として受け取り、-> T は戻り値の型を指定します。関数を呼び出す際、Rustは具体的な型を推論します:
let num = identity(42); // T は i32 です
let text = identity("hello"); // T は &str です
構造体と同様に、関数内でも複数のジェネリックパラメータを使用することができます。
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // (i32, &str) を返します
ジェネリック関数を使用すると、再利用可能なロジックを一度記述するだけで多くの型に適用できるようになり、型の安全性を維持しながらコードの重複を減らすことができます。
チャレンジ
簡単どんな型でも動作するジェネリック関数を備えたユーティリティモジュールを作成しましょう!特定の構造体に縛られることなく、ジェネリクスによってコードがいかに柔軟で再利用可能になるかを示す、スタンドアロンの関数を作成します。
コードは2つのファイルに分けて構成します:
utils.rs: 公開されたジェネリックユーティリティ関数のコレクションを作成します:wrap_in_pair<T>— 単一の値を受け取り、その値を2回含むタプル(value, value)を返します。これには型がCloneである必要があるため、<T: Clone>を使用してください。swap<T, U>— 潜在的に異なる型の2つの値を受け取り、それらを逆の順序でタプル(U, T)として返します。
main.rs: ユーティリティモジュールをインポートし、これらのジェネリック関数が異なる型で動作することを実証します。同じ関数定義が整数、浮動小数点数、文字列をシームレスに処理する様子を示してください。
メインファイルでは、以下の手順でユーティリティ関数を実証してください:
- 整数(最初の入力、
i32としてパース)でwrap_in_pairを使用し、両方の要素を出力する。 - 文字列(2番目の入力)で
wrap_in_pairを使用し、両方の要素を出力する。 - 整数(3番目の入力、
i32としてパース)と文字列(4番目の入力)でswapを使用し、入れ替えられた結果を出力する。
出力は以下の形式に従う必要があります:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})例えば、入力が 5、hello、42、world の場合:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)wrap_in_pair が整数と文字列の両方で全く同じように動作し、swap が2つの完全に異なる型を処理していることに注目してください。これがジェネリック関数の柔軟性です!
4つの入力を受け取ります:整数、文字列、別の整数、そして別の文字列です。
チートシート
ジェネリック関数を使用すると、任意の型で動作する独立したユーティリティ関数を記述できます。関数名の後のアングルブラケット(山括弧)内でジェネリックパラメータを宣言します。
fn identity<T>(value: T) -> T {
value
}
<T> はジェネリック型パラメータを宣言しており、これは関数のパラメータや戻り値の型で使用できます。Rustは、関数を呼び出す際に具体的な型を推論します。
let num = identity(42); // T は i32
let text = identity("hello"); // T は &str
1つの関数で複数のジェネリックパラメータを使用することもできます。
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // (i32, &str) を返す
ジェネリック型に特定の機能が必要な場合は、トレイト境界(trait bounds)を使用します。
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
自分で試してみよう
mod utils;
use std::io;
fn main() {
// 4つの入力を読み込む
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: num1でwrap_in_pairを使用し、結果を表示する
// 形式: Pair of ints: ({value}, {value})
// TODO: str1でwrap_in_pairを使用し、結果を表示する
// 形式: Pair of strings: ({value}, {value})
// TODO: num2とstr2でswapを使用し、入れ替えられた結果を表示する
// 形式: Swapped: ({string}, {int})
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。