Menu
CoddyTech

ジェネリック関数

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) を返す

ジェネリック関数を使うと、再利用可能なロジックを一度だけ記述して、さまざまな型に適用できます。これにより、型安全性を保ちながらコードの重複を減らせます。

challenge icon

チャレンジ

簡単

あらゆる型で使えるジェネリック関数を備えたユーティリティモジュールを作りましょう!特定の構造体に依存せず、ジェネリクスによってコードの柔軟性と再利用性が高まることを示す、独立した関数を作成します。

コードを2つのファイルに分けて整理します。

  • utils.rs: パブリックなジェネリックユーティリティ関数をまとめて作成します。
    • wrap_in_pair<T>: 1つの値を受け取り、その値を2回含むタプルを返します。(value, value) 型に Clone が必要なので、<T: Clone> を使います。
    • swap<T, U>: 型が異なる可能性のある2つの値を受け取り、順序を逆にしたタプル (U, T) として返します。
  • main.rs: ユーティリティモジュールをインポートし、異なる型でこれらのジェネリック関数が動作することを示します。同じ関数定義で整数、浮動小数点数、文字列をシームレスに扱えることを示しましょう。

メインファイルで、次のようにユーティリティ関数を使ってみましょう。

  1. 整数(1つ目の入力。i32 として解析)に対して wrap_in_pair を使い、両方の要素を出力する
  2. 文字列(2つ目の入力)に対して wrap_in_pair を使い、両方の要素を出力する
  3. 整数(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})
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rustオンラインコンパイラ