ジェネリック関数
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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>: 1つの値を受け取り、その値を2回含むタプルを返します。(value, value)型にCloneが必要なので、<T: Clone>を使います。swap<T, U>: 型が異なる可能性のある2つの値を受け取り、順序を逆にしたタプル(U, T)として返します。
main.rs: ユーティリティモジュールをインポートし、異なる型でこれらのジェネリック関数が動作することを示します。同じ関数定義で整数、浮動小数点数、文字列をシームレスに扱えることを示しましょう。
メインファイルで、次のようにユーティリティ関数を使ってみましょう。
- 整数(1つ目の入力。
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つです。整数、文字列、もう1つの整数、もう1つの文字列を受け取ります。
自分で試してみよう
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})
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ