トレイトのベクタ
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 46/61。
Box<dyn Trait>によって、1つの変数で異なる型を保持できることを学びました。これをコレクションと組み合わせると、本当の力が発揮されます。つまり、1つのベクターに複数の異なる型を格納できるのです。
通常のベクタでは、すべての要素が同じ型である必要があります。Vec<Dog>に猫も含めることはできません。しかし、トレイトオブジェクトを使えば、共通のトレイトを実装するあらゆるものを保持するベクタを作成できます。
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
型 Vec<Box<dyn Speak>> は「Box化されたトレイトオブジェクトのベクター」を意味します。各要素は Speak を実装する何かを指す Box であり、実際の型は異なっていてもかまいません。ベクターに追加するときは、各値を Box::new() でラップします。
このパターンは、UI コンポーネント、ゲームエンティティ、プラグインシステムなど、関連しているものの異なるオブジェクトのコレクションを管理する必要がある柔軟なシステムを構築するうえで不可欠です。
チャレンジ
簡単さまざまな種類のアラートを処理できる通知システムを構築しましょう!トレイトオブジェクトを使って、さまざまな通知タイプ、メール、SMS メッセージ、プッシュ通知をすべて単一のベクターに格納するコレクションを作成します。
コードを2つのファイルに分けて整理します。
notifications.rs:sendメソッドを持つ公開Notifyトレイトを定義します。このメソッドは&selfを受け取り、送信される通知を説明するStringを返します。次に、3つの公開構造体を作成します。Email:公開recipientフィールド(String)を持ちます。そのsendはEmail to: {recipient}を返す必要があります。Sms:公開phoneフィールド(String)を持ちます。そのsendはSMS to: {phone}を返す必要があります。Push:公開deviceフィールド(String)を持ちます。そのsendはPush to: {device}を返す必要があります。
main.rs:notificationsモジュールを取り込み、3つすべての通知タイプを保持するVec<Box<dyn Notify>>型のベクターを作成します。指定された入力を使って各通知タイプを1つずつ作成し、順番(Email、Sms、Push)にベクターへ追加してから、ベクターの最初の要素に対してsend()を呼び出した結果を出力します。
ここでの重要な概念は、Email、Sms、Pushが完全に異なる構造体であるにもかかわらず、すべてNotifyトレイトを共有しているため、同じベクター内に共存できるということです。各要素はBox::new()でラップされ、トレイトオブジェクトが作成されます。
出力には最初の通知が表示されます。
Email to: {recipient}たとえば、入力がalice@example.com、555-1234、iPhone-12の場合:
Email to: alice@example.com3つの入力(メールの受信者、電話番号、デバイス名)を受け取ります。
自分で試してみよう
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// 入力を読み取る
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: 異なる通知タイプを保持する Vec<Box<dyn Notify>> を作成する
// TODO: 入力を使って Email、Sms、Push のインスタンスを作成する
// TODO: Box::new() を使ってそれらを順番に (Email, Sms, Push) ベクターに追加する
// TODO: 最初の要素で send() を呼び出した結果を出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ