関連型、ジェネリック型、スーパートレイト、ニュータイプパターン。
成果物
参考
関連型
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; } struct Counter { count: u32 } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { None } }
type Item;
の記述が関連型である。
なぜ、以下のようにジェネリクスを使わないのか? 各実装で型を注釈せねばならないから。Iterator<String> for Counter
など。これは複数回定義できることを意味する。関連型なら1度のみの実装になる。
pub trait Iterator<T> { fn next(&mut self) -> Option<T>; }
演算子オーバーロード
use std::ops::Add; #[derive(Debug, PartialEq)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, Point { x: 3, y: 3 }); }
std::ops::Add
トレイトは以下。
trait Add<RHS=Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; }
RHS=Self
はデフォルト型引数。RHS
という型は未指定のときSelf
型となる。
以下、Self
でない型を使う例。
use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } }
同名メソッドを呼ぶ
trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) {println!("This is your captain speaking.");} } impl Wizard for Human { fn fly(&self) {println!("Up!");} } impl Human { fn fly(&self) {println!("*waving arms furiously*");} }
fn main() { let person = Human; person.fly(); }
$ rustc main.rs $ ./main *waving arms furiously*
トレイト名::メソッド名(&インスタンス);
で各同名メソッドを呼べる。
fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); }
$ ./main This is your captain speaking. Up! *waving arms furiously*
だが、関連型にはself
がない。
trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } fn main() { println!("A baby dog is called a {}", Dog::baby_name()); }
$ ./main A baby dog is called a Spot
もしAnimal::baby_name()
とすれば、どのメソッドを使うか特定できず以下エラーになる。
println!("A baby dog is called a {}", Animal::baby_name());
$ rustc main.rs error[E0283]: type annotations required: cannot resolve `_: Animal` --> main.rs:21:43 | 21 | println!("A baby dog is called a {}", Animal::baby_name()); | ^^^^^^^^^^^^^^^^^ | note: required by `Animal::baby_name` --> main.rs:6:5 | 6 | fn baby_name() -> String; | ^^^^^^^^^^^^^^^^^^^^^^^^^
もし<Dog as Animal>::baby_name()
とすれば、impl Dog { baby_name() }
が呼ばれる。
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
$ ./main A baby dog is called a puppy
trait::method(&instance);
<type as trait>::method();
スーパートレイト
use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } }
trait OutlinePrint: fmt::Display {}
は、OutlinePrint
はfmt::Display
を必要とする、という意味。
以下で実装する。
impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } }
use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } } struct Point { x: i32, y: i32, } impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl OutlinePrint for Point {} fn main() { let p = Point { x: 1, y: 3 }; p.outline_print(); }
$ rustc main.rs $ ./main ********** * * * (1, 3) * * * **********
ニュータイプパターン
外部の型に外部のトレイトを実装する。
オーファンルールにより、型にトレイトを実装するにはトレイトか型がローカルクレートである必要がある。ニュータイプパターンはこれを回避できる。
タプル構造体に新しい型を作成することで。
例としてVec<T>
型にfmt::Display
トレイトを実装する。
use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); }
$ rustc main.rs $ ./main w = [hello, world]
ただし、Vec型の既存メソッドがWrapper
にはない。
対象環境
- 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のオブジェクト指向
- RustのOOP(トレイトオブジェクト)
- Rustのオブジェクト指向(デザインパターン)
- Rustのパターン
- Rustのパターン(論駁可能性)
- Rustパターン(記法)
- Rustの高度な機能(Unsafe Rust)
- Rustの高度な機能(ライフタイム)