ジェネリック構造体
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 24/61。
これまで作成したすべてのstructには、特定の具体的なtypeのfieldがありました。PetにはStringのnameがあり、Rectangleにはf64のdimensionsがあります。しかし、any typeのdataをholdできるstructを作成したい場合はどうすればよいでしょうか?
ここでジェネリクスの出番です。ジェネリクスを使うと、複数の型で動作する柔軟で再利用可能なコードを書けます。特定の型をハードコードする代わりに、通常は T というプレースホルダーを使います。このプレースホルダーは、struct を使うときに具体的な型に置き換えられます。
任意の型を保持できるシンプルなラッパー構造体を次に示します。
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やジェネリックを備えた他のほとんどの言語で標準的に使われています。
チャレンジ
簡単任意の型の値を保持できる generic container を作成しましょう!Container struct を作成して、generics によってコードが柔軟で再利用可能になる仕組みを示します。
コードを2つのファイルに分けて整理します。
container.rs:Container<T>という名前の単一の public fielditem(型はT)を持つ public generic struct を Define します。この container は、integer、float、string、その他あらゆる型を保持できる should があります。main.rs:container module を取り込み、異なる型の data を保持する containers を Create して、その柔軟性を示します。3つの containers を Create し、それぞれが hold する contents を表示します。
main file で、次の containers を Create します。
- integer value を保持する container(first input を
i32として parse) - float value を保持する container(second input を
f64として parse) - string を保持する container(third input を
Stringとして保持)
output には、各 container の contents を次の format で表示します。
Integer container: {value}
Float container: {value}
String container: {value}たとえば、inputs が 42、3.14、hello の場合:
Integer container: 42
Float container: 3.14
String container: hello同じ Container<T> Define が、3つの異なる type すべてで動作することに注目してください。これが generics の力です!
3つの inputs、つまり integer value、float value、string value を receive します。
自分で試してみよう
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: Create three containers using Container<T>
// 1. An integer container holding int_value
// 2. A float container holding float_value
// 3. A string container holding string_value
// TODO: 各コンテナの内容を指定の形式で出力する
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ