ジェネリックメソッド
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 25/61。
ジェネリックな構造体は任意の型を保持できますが、そのデータとやり取りするためのメソッドがなければ、あまり有用ではありません。ジェネリックな構造体にメソッドを定義するには、implブロックで特別な構文を使用する必要があります。
重要なのは、impl 自体にジェネリックパラメータを宣言することです:
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn get(&self) -> &T {
&self.value
}
}
Wrapper<T> の前にある impl<T> に注目してください。これは、T が実装ブロック全体に対するジェネリックな型パラメータであることを Rust に伝えます。この宣言がないと、Rust は T という名前の具体的な型を探し、それを見つけることができずに失敗します。
getメソッドは、ラッパーが保持している任意の型への参照である&Tを返します。これは、Tが整数、文字列、またはその他の型であっても機能します:
let num_wrapper = Wrapper { value: 100 };
let text_wrapper = Wrapper { value: "Rust" };
println!("{}", num_wrapper.get()); // 100
println!("{}", text_wrapper.get()); // Rust
ジェネリックな T がコンパイル時に各具体的な型に適応するため、同じメソッド定義が両方に対して機能します。
チャレンジ
簡単ジェネリックなコンテナをメソッドで拡張してみましょう!任意の型を保持でき、その中身を操作するためのメソッドを提供する Box 構造体(Rust標準の Box と混同しないでください)を構築します。
コードは2つのファイルに分けて構成します:
mybox.rs:T型のプライベートフィールドcontentsを持つ、MyBox<T>という名前のパブリックなジェネリック構造体を定義します。この構造体に以下のメソッドを実装してください:- 与えられた値で新しい
MyBoxを作成するnew関連関数 - 中身への参照を返す
peekメソッド(&selfを使用) - 新しい値を受け取り、現在の中身を置き換える
replaceメソッド(&mut selfを使用)
- 与えられた値で新しい
main.rs: モジュールを取り込み、異なる型で動作するジェネリックメソッドを実演します。ボックスを作成し、その中身を覗き(peek)、値を置き換えて、メソッドの動作を確認します。
ジェネリック構造体にメソッドを実装する際の重要な構文を思い出してください。MyBox<T> の前に impl<T> を記述して、T が実装ブロック全体に対するジェネリックパラメータであることをRustに伝える必要があります。
メインファイルでは、以下の手順で MyBox を実演してください:
- 整数(最初の入力、
i32としてパース)でボックスを作成する - その中身を覗き、値を表示する
- 中身を新しい整数(2番目の入力、
i32としてパース)で置き換える - 再度中身を覗き、更新された値を表示する
- 文字列(3番目の入力)で2つ目のボックスを作成する
- 文字列ボックスの中身を覗く
出力は以下の形式に従ってください:
Integer box contains: {value}
After replace: {value}
String box contains: {value}例えば、入力が 10、25、Rust の場合:
Integer box contains: 10
After replace: 25
String box contains: Rust3つの入力を受け取ります:初期の整数、置き換え用の整数、そして文字列の値です。
チートシート
ジェネリックな構造体にメソッドを定義するには、implブロックでジェネリックパラメータを宣言します。
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn get(&self) -> &T {
&self.value
}
}
impl<T>という構文は、Tが実装ブロック全体に対するジェネリックな型パラメータであることをRustに伝えます。これがないと、RustはTという名前の具体的な型を探してしまいます。
メソッドはジェネリック型への参照(&T)を返すことができ、これはあらゆる具体的な型に対して機能します。
let num_wrapper = Wrapper { value: 100 };
let text_wrapper = Wrapper { value: "Rust" };
println!("{}", num_wrapper.get()); // 100
println!("{}", text_wrapper.get()); // Rust
自分で試してみよう
mod mybox;
use mybox::MyBox;
fn main() {
// 入力を読み込む
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let initial_int: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let replacement_int: i32 = input2.trim().parse().expect("Invalid integer");
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: 初期値の整数で MyBox を作成する
// TODO: その内容を覗き見て、"Integer box contains: {value}" と出力する
// TODO: 内容を置換用の整数で置き換える
// TODO: 再度覗き見て、"After replace: {value}" と出力する
// TODO: 文字列の値で2つ目の MyBox を作成する
// TODO: 文字列のボックスを覗き見て、"String box contains: {value}" と出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。