ジェネリック構造体
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 24/61。
これまで、作成したすべての構造体には、特定の具体的な型のフィールドがありました。Pet は String 型の名前を持ち、Rectangle は f64 型の寸法を持っています。しかし、あらゆる型のデータを保持できる構造体を作成したい場合はどうすればよいでしょうか?
ここでジェネリクスが登場します。ジェネリクスを使用すると、複数の型で動作する、柔軟で再利用可能なコードを記述できます。特定の型をハードコーディングする代わりに、プレースホルダー(通常は T)を使用します。これは、構造体を使用する際に具体的な型に置き換えられます。
あらゆる型を保持できるシンプルなラッパー構造体は次の通りです:
struct Wrapper<T> {
value: T,
}
構造体名の後にある <T> は、ジェネリック型パラメータを宣言しています。構造体の中では、T は実際に使用するあらゆる型の代わりとして機能します。インスタンスを作成すると、Rust は具体的な型を推論します:
let int_wrapper = Wrapper { value: 42 }; // T は i32 です
let float_wrapper = Wrapper { value: 3.14 }; // T は f64 です
let text_wrapper = Wrapper { value: "hello" }; // T は &str です
これらはそれぞれ異なる具体的な型(Wrapper<i32>、Wrapper<f64>、および Wrapper<&str>)ですが、すべて同じ構造体定義を共有しています。これにより、本質的に同じことを行う IntWrapper、FloatWrapper、および StringWrapper 構造体を個別に記述する必要がなくなります。
文字 T は単なる慣習("Type" の略)です。任意の有効な識別子を使用できますが、T は Rust やジェネリクスを持つ他のほとんどの言語における標準です。
チャレンジ
簡単どんな型の値でも保持できる汎用的なコンテナを作成しましょう!ジェネリクスによってコードがいかに柔軟で再利用可能になるかを示す Container 構造体を作成します。
コードは2つのファイルに分けて構成します:
container.rs:T型の単一のパブリックフィールドitemを持つ、Container<T>という名前のパブリックなジェネリック構造体を定義します。このコンテナは、整数、浮動小数点数、文字列など、あらゆる型を保持できる必要があります。main.rs: コンテナモジュールを取り込み、異なる型のデータを保持するコンテナを作成することで、その柔軟性を実証します。3つのコンテナを作成し、それぞれが何を保持しているかを表示します。
メインファイルで、以下のコンテナを作成してください:
- 整数値を保持するコンテナ(最初の入力、
i32としてパースされたもの) - 浮動小数点数値を保持するコンテナ(2番目の入力、
f64としてパースされたもの) - 文字列を保持するコンテナ(3番目の入力、
Stringとして保持されたもの)
出力は、各コンテナの内容を以下の形式で表示する必要があります:
Integer container: {value}
Float container: {value}
String container: {value}例えば、入力が 42、3.14、hello の場合:
Integer container: 42
Float container: 3.14
String container: hello同じ Container<T> の定義が、3つの異なる型すべてに対してどのように機能するかに注目してください。これがジェネリクスの力です!
整数値、浮動小数点数値、および文字列値の3つの入力を受け取ります。
チートシート
ジェネリクスを使用すると、型パラメータのプレースホルダを使用して、あらゆる型のデータを保持できる構造体を作成できます。
ジェネリックな構造体を宣言するには、型パラメータ(慣習的には T)を山括弧で囲んで使用します:
struct Wrapper<T> {
value: T,
}
<T> はジェネリックな型パラメータを宣言し、構造体内部の T は実際の型のプレースホルダとして機能します。
インスタンスを作成する際、Rustは具体的な型を推論します:
let int_wrapper = Wrapper { value: 42 }; // Tはi32
let float_wrapper = Wrapper { value: 3.14 }; // Tはf64
let text_wrapper = Wrapper { value: "hello" }; // Tは&str
各インスタンスは、同じ構造体定義を共有しながら、異なる具体的な型(Wrapper<i32>、Wrapper<f64>、Wrapper<&str>)になります。
自分で試してみよう
mod container;
use container::Container;
fn main() {
// 3つの入力を読み込む
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Container<T> を使用して3つのコンテナを作成する
// 1. int_value を保持する整数コンテナ
// 2. float_value を保持する浮動小数点コンテナ
// 3. string_value を保持する文字列コンテナ
// TODO: 各コンテナの内容を所定の形式で出力する
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。