トレイト境界の構文
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 40/61。
これまで、trait を定義し、struct に実装する方法を学びました。また、柔軟なコードを書くためにジェネリクスも扱いました。ここでは、これらの概念を組み合わせます。trait を使って、ジェネリック関数が受け入れられる型を制約する方法です。
パラメータに対して特定のmethodをcallする必要があるgeneric functionを考えてみましょう。何の制約もない場合、Rustは型 T がそのmethodを持つという保証を得られません。
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // エラー!T は summarize() を持っていないかもしれない
}
解決策はトレイト境界です。ジェネリックパラメーターの後に: TraitNameを追加することで、Tがそのトレイトを実装しなければならないことをRustに伝えます。
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // これでRustはこのメソッドが存在することを知っている
}
構文 <T: Summary> は、「T は Summary を実装する任意の型である」と読みます。これで、この関数は必要な動作を持つ型だけを受け入れるようになります。Summary を実装していない型を渡そうとすると、コンパイラーは明確なエラーを出して拒否します。
このパターンが強力なのは、特定の機能にもアクセスできる generic code を記述できるからです。あなたの function は柔軟性を保ち、any type で動作しますが、必要な振る舞いを提供する型に限られます。
チャレンジ
簡単適切にフォーマットされたメッセージだけを送信できるよう、トレイト境界を使用する通知システムを構築しましょう!特定のトレイトを実装する任意の型を受け取る generic function を作成し、トレイト境界によってジェネリックパラメーターを制約する方法を示します。
コードを次の2つのファイルに整理します。
notification.rs:messageという名前のメソッドを持ち、&selfを受け取り、Stringを返す publicNotifyトレイトを定義します。次に、このトレイトを実装する2つのpublic構造体を作成します。Email:publicなsubjectフィールド(String)を持ちます。そのmessageメソッドはEmail: {subject}を返す必要があります。SMS:publicなcontentフィールド(String)を持ちます。そのmessageメソッドはSMS: {content}を返す必要があります。
Notifyトレイトを実装する任意の型Tを受け取る、send_notificationという名前のpublicな generic function を作成します。この function は、item に対してmessage()を calling した結果を表示する必要があります。main.rs:notificationモジュールを取り込み、提供された inputs を使ってEmailとSMSの両方の instance を作成します。それぞれに対してsend_notificationを Call し、Notifyを実装する任意の型で generic function が動作することを示します。
ここでの重要なポイントは、send_notificationが受け取る具体的な型を知らないということです。その型が message を生成できることだけを知っています。トレイト境界T: Notifyによって、この capability が保証されます。
Your output should show both notifications being sent:
Email: {subject}
SMS: {content}For example, with inputs Meeting Tomorrow and On my way!:
Email: Meeting Tomorrow
SMS: On my way!You will receive two inputs: the email subject and the SMS content.
自分で試してみよう
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// 入力を読み取る
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: subjectでEmailインスタンスを作成する
// TODO: contentでSMSインスタンスを作成する
// TODO: emailでsend_notificationを呼び出す
// TODO: smsでsend_notificationを呼び出す
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ