Vec<Box<MyTrait>>
とすることでtrait
(インタフェース)を実装した型を受け入れる。
成果物
異なる型を許容する
たとえばGUIライブラリを作るとき、draw
メソッドを持った異なるコンポーネントを作りたいとする。将来、新たなコンポーネントを作りうる。
- Component
- Button
- TextField
- Image
- SelectBox
component.draw();
component
はButton
やTextField
など様々な型でありうる。
$ cargo new gui --lib
一般的な振る舞いをトレイトで定義する
トレイトオブジェクト。
src/lib.rs
pub trait Draw { fn draw(&self); }
トレイトオブジェクトは、トレイトを実装する型のインスタンスを指す。Box<T>
などのポインタで指定する必要がある。
pub struct Screen { pub components: Vec<Box<Draw>>, }
impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } }
トレイト境界を用いると以下のように書ける。
pub struct Screen<T: Draw> { pub components: Vec<T>, } impl<T> Screen<T> where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } }
これにてBox<Button>
とBox<TextField>
を含むVec<T>
を保持できる。
Button
pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { // code to actually draw a button // 実際にボタンを描画するコード } }
src/lib.rs
pub trait Draw { fn draw(&self); } pub struct Screen<T: Draw> { pub components: Vec<T>, } impl<T> Screen<T> where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } #[derive(Debug)] pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { println!("{:?}", self); } }
src/main.rs
use gui::{Screen,Button}; use gui::Draw; #[derive(Debug)] struct SelectBox { width: u32, height: u32, options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { println!("{:?}", self); } } fn main() { let screen = Screen { components: vec![ Box::new(SelectBox { width: 75, height: 10, options: vec![ String::from("Yes"), String::from("Maybe"), String::from("No") ], }), Box::new(Button { width: 50, height: 10, label: String::from("OK"), }), ], }; screen.run(); }
$ cargo run
コンパイルできない……。ドキュメントと何が違うの?
$ cargo run ... error[E0308]: mismatched types --> src/main.rs:26:22 | 26 | Box::new(Button { | ______________________^ 27 | | width: 50, 28 | | height: 10, 29 | | label: String::from("OK"), 30 | | }), | |_____________^ expected struct `SelectBox`, found struct `gui::Button` | = note: expected type `SelectBox` found type `gui::Button` error[E0277]: the trait bound `std::boxed::Box<SelectBox>: gui::Draw` is not satisfied --> src/main.rs:15:18 | 15 | let screen = Screen { | ^^^^^^ the trait `gui::Draw` is not implemented for `std::boxed::Box<SelectBox>` | = note: required by `gui::Screen` error[E0599]: no method named `run` found for type `gui::Screen<std::boxed::Box<SelectBox>>` in the current scope --> src/main.rs:33:12 | 33 | screen.run(); | ^^^ | = note: the method `run` exists but the following trait bounds were not satisfied: `std::boxed::Box<SelectBox> : gui::Draw`
やはりVec
型は最初の型と同じものしか受け付けないじゃないか……騙された。トレイトオブジェクトなら解決できるんじゃなかったの? トレイトオブジェクトとは一体……。それを学習する項目なのに、成功するコードがないから何もわからないまま……。
Draw
未実装インスタンスを追加するとエラー
error[E0277]: the trait bound `std::boxed::Box<std::string::String>: gui::Draw` is not satisfied --> src/main.rs:4:18 | 4 | let screen = Screen { | ^^^^^^ the trait `gui::Draw` is not implemented for `std::boxed::Box<std::string::String>` | = note: required by `gui::Screen`
これは期待通り。
できた
ドキュメントのコード紛らわしい。完全なコードを残しておく。
$ cargo new gui --lib
src/lib.rs
pub trait Draw { fn draw(&self); } pub struct Screen { pub components: Vec<Box<Draw>>, } impl Screen { pub fn run(&self) { for c in self.components.iter() { c.draw(); } } } #[derive(Debug)] pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { println!("Button: {:?}", self); } }
src/main.rs
use gui::{Screen,Button,Draw}; #[derive(Debug)] struct SelectBox { width: u32, height: u32, options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { println!("SelectBox: {:?}", self); } } fn main() { let screen = Screen { components: vec![ Box::new(SelectBox { width: 75, height: 10, options: vec![ String::from("Yes"), String::from("Maybe"), String::from("No") ], }), Box::new(Button { width: 50, height: 10, label: String::from("OK"), }), ], }; screen.run(); }
$ cargo run ... SelectBox: SelectBox { width: 75, height: 10, options: ["Yes", "Maybe", "No"] } Button: Button { width: 50, height: 10, label: "OK" }
ポイントはトレイト定義pub components: Vec<Box<Draw>>,
のところ。Vec
の要素の型はtrait
のポインタらしい。
なんか直感的じゃない。呼出元ではVec
の要素に、trait
であるDraw
のメソッドを実装したButton
, SelectBox
を渡している。それらはコンポーネントの具体名であり、Draw
を匂わせるものではない。オブジェクト指向の継承のほうがわかりやすい。
所感
「ポリモーフィズム=多態性=多様性=多相性」ってことらしい。表記ゆれしすぎ。英語ならpolymorphism
。
これをRustのトレイト境界にて実現する方法を「パラメータ境界多相性(bounded parametric polymorphism)」と呼ぶってことか?
そもそもトレイト境界もよくわからん。where T: Clone, Debug
とかのヤツじゃないの? ジェネリックで何でも許しておいて絞り込むヤツ。今回のコードも<>
使ってるからジェネリックであり、トレイトで絞り込んでるっぽいからトレイト境界ってことなの?
C#言語などの継承はクラス単位の多相性。でもRust言語のパラメータ境界多相性はメソッド単位の多相性。Rustのほうが無駄なメソッドを継承せずに済むし、厳密に絞りこめる。でも処理をまとめたいとき、膨大な数のトレイト境界を書くハメになりそう。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux
前回まで
- Rustを学んでみたい(プログラミング言語)
- Rustの環境構築
- RustでHelloWorld
- Rustの和訳ドキュメント
- Cargoでプロジェクト作成・ビルド・実行
- クレートとは?
- Rustで関数を使ってみる
- Rustでモジュールを使ってみる
- Rustで乱数を生成する(rand)
- Rustで標準入力する(std::io::stdin().read_line())
- RustでMatch判定する(match)
- Rustでprintとread_lineを1行にする方法
- Rustで数当てゲーム
- クレート名にドット.が使えない
- Rustの変数と可変性(let, mut) error[E0384]: cannot assign twice to immutable variable
x
- Rustのimmutable束縛とconst定数の違い
- RustのREPL、evcxrのインストールに失敗した
- Rustでコンパイルするときの変数未使用warningを消す
- Rustの変数(再代入、再宣言(シャドーイング))
- Rustのシャドーイングについて
- イミュータブルについて(副作用)
- Rustの定数(const)
- Rustのデータ型(数値)
- Rustのデータ型(論理)
- Rustのデータ型(文字)
- Rustのデータ型(タプル)
- Rustのデータ型(配列)
- Rustの関数
- Rustのif式
- Rustのくりかえし文(loop)
- Rustのくりかえし文(while)
- Rustのくりかえし文(for)
- Rustの所有権(ムーブ)
- Rustの所有権(関数)
- Rustの所有権(スライス)
- Rustの構造体(定義とインスタンス化)
- Rustの構造体(プログラム例)
- Rustの構造体(メソッド)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustの列挙型(enum)
- Rustのmatch(制御フロー演算子)
- RustでNULLを扱う(Option, Some, None)
- NULL参照は10億ドルの失敗だった
- Rustの列挙型に独自表示を実装する(E0277 対策 std::fmt::Display 実装)
- RustのIfLet(matchの糖衣構文)
- Rustのプロジェクト構造
- Rustのcargoでライブラリ&テスト(単体、結合)
- Rustのモジュール(mod)
- Rustのモジュール(pub)
- Rustのmod参照方法(
mod 子モジュール名;
,use 要素名;
,extern crate クレート名;
,super
) - Rustのインポートまとめ(Rust2018)
- RustのコレクションVec型
- RustのコレクションString型
- RustのコレクションHashMap型
- Rustのコレクション(練習問題)
- Rustのエラー処理
- Rustのジェネリクス
- Rustのトレイト
- Rustのライフタイム1
- Rustのライフタイム2(構造体の定義)
- Rustのライフタイム3(ライフタイム省略)
- Rustのライフタイム4(impl定義)
- Rustの静的ライフタイム5('static)
- Rustのライフタイム6(ジェネリクス、トレイト境界とともに)
- Rustのテストコードを書く
- Rustのテスト実行
- Rustのテスト体系化
- Rustでコマンドライン引数を受け取る
- Rustのファイル読込
- Rustでリファクタリング(モジュール性とエラー処理の向上)
- Rustでテスト駆動開発
- Rustで環境変数を取得する
- RustでStdErr出力
- Rustのクロージャ
- Rustのイテレータ
- Rustのイテレータ(Minigrep改善)
- Rustのイテレータ(パフォーマンス)
- Rustのイテレータ(Minigrep改善)
- Rustのcargo(ビルドのカスタマイズ)
- Rustのcargo(cargo docでドキュメント生成)
- Rustのエクスポート(pub use)
- Rustのクレートを公開する方法(crates.io)
- Rustのcargoでワークスペースをつくる
- Rustのcargo installでバイナリをインストールする
- Rustのcargoを拡張する方法
- Rustのスマートポインタ
- スマートポインタBox
- Rustのスマートポインタ(Derefトレイト)
- Rustのスマートポインタ(Dropトレイト)
- Rustのスマートポインタ(Rc
) - Rustのスマートポインタ(RefCell
) - Rustのスマートポインタ(Weak
) - Rustのスレッド
- Rustのスレッド(メッセージ送受信)
- Rustのスレッド(Mutex、Arc)
- Rustのスレッド(Send、Syncトレイト)
- Rustのオブジェクト指向