100日後にRustをちょっと知ってる人になる: [Day 85]書籍: Rust プログラミング完全ガイド その9
Day 85 のテーマ
Day 84 までに Rust の書籍の Rustプログラミング完全ガイド の 1 章から 18 章までを読み終わりました。
- 第1章 Rustを始めよう
- 第2章 数値演算などの基本を把握しよう
- 第3章 オブジェクトに名前を付ける
- 第4章 実行の流れを制御する
- 第5章 データシーケンスを使う
- 第6章 基本のデータ型を使う
- 第7章 列挙と照合
- 第8章 混成的なデータ構造を使う
- 第9章 関数を定義する
- 第10章 ジェネリックな関数や型を定義する
- 第11章 メモリを割り当てる
- 第12章 データの実装
- 第13章 クロージャを定義する
- 第14章 変更可能な文字列を使う
- 第15章 範囲とスライス
- 第16章 イテレータを使う
- 第17章 入出力とエラー処理
- 第18章 データのカプセル化[メソッドとモジュール]
- 第19章 トレイトを使う
- 第20章 オブジェクト指向プログラミング
- 第21章 標準ライブラリのコレクション
- 第22章 所有権、移動、コピー
- 第23章 借用とライフタイム
- 第24章 さらにライフタイムについて
第19章 トレイトを使う
この章での内容:
- トレイトを使えば、ジェネリック関数の呼び出しに関するコンパイラのエラーメッセージが読みやすくなる>こと
- ジェネリックパラメータの制約が 1 つにまとまったり、複数のトレイトに分かれたりする理由
- ジェネリックパラメータに制約がないと、できることがどれほど少ないか
- トレイトに入れた関数のスコープ
- 複数のメソッドを含むトレイトを作る方法
- 複数のトレイトに渡すジェネリックパラメータを制約する方法
- トレイト継承を使う方法
- トレイトを使って外部型にメソッドを追加する方法
- 標準トレイトの
Display
とDebug
を実装する方法 - 便利なジェネリックトレイトを宣言する方法
- 型指定を持たないトレイトをシンプルに使える関連型
- イテレータの実装
トレイトの概要についてメモ
Day 17 などの振り返りになりますが、トレイトとはどういうものなのか、大事なことなので改めて自分のことばで表してみます。
他の言語仕様との比較をしながら、いろいろと振り返りを行っているのでトレイトについても Java ではどういうことかを考えてみます。
まず、トレイトの定義ですが、様々な型に対して抽象化した任意の振る舞い (関数) を持たせることができる言語仕様でした。つまり、トレイトとして意味のあるまとまりとして振る舞い (関数) のシグネチャを定義します。この時点では振る舞いの実体はなくてよくて、抽象化したシグネチャとして定義できていればよいです。この抽象化した振る舞いが定義されたトレイトを、何らかの型 (構造体など) に対して関連付け (実装) を行って使用します。
つまり、トレイトとは Java で言うところの、インターフェースや抽象クラスのような役割をするものになります。
本に書かれている例として、平行根をもとめるメソッドを浮動小数点型 f32
や f64
に持たせることを行っていました。まずトレイトとしては次のように定義をします。
trait HasSqareRoot {
fn sqrt(self) -> Self;
}
その実装としてそれぞれ次のようにしています。
impl HasSquareRoot for f32 {
fn sq_root(self) -> Self { self.sqrt() }
}
impl HasSquareRoot for f64 {
fn sq_root(self) -> Self { self.sqrt() }
}
他の例でも考えてみます。例えば、よくある演算として面積を求めるものを考えてみます。三角形と四角形、そして台形では面積の求め方がことなります。しかし、"面積を求める"という振る舞い自体は共通です。振る舞いが同じで挙動が異なるということになります。そこで、まず面積を求めるという振る舞いをするメソッドをトレイトに定義します。
trait Area {
fn calc(&self) -> f64;
}
そして面積を求める対象の形に関する構造体を定義します。
struct Triangle {
base: f64,
height: f64,
}
struct Rectangle {
base: f64,
height: f64,
}
struct Trapezoid {
top_base: f64,
bottom_base: f64,
height: f64,
}
この形に対して面積を求めるトレイトを実装します。
impl Area for Triangle {
fn calc(&self) -> f64 {
(self.base * self.height) / 2.0
}
}
impl Area for Rectangle {
fn calc(&self) -> f64 {
self.base * self.height
}
}
impl Area for Trapezoid {
fn calc(&self) -> f64 {
(self.top_base + self.bottom_base) * self.height / 2.0
}
}
let triangle = Triangle {base: 10.0, height: 20.0};
let rectangle = Rectangle {base: 10.0, height: 20.0};
let trapezoid = Trapezoid {top_base: 10.0, bottom_base: 20.0, height: 10.0};
println!("三角形: {}", triangle.calc);
println!("四角形: {}", rectangle.calc);
println!("台形: {}", trapezoid.calc);
Day 85 のまとめ
この書籍の中でトレイトがこの後半になって出てくるのが少し意外でした。トレイトをつかって様々な振る舞いを複数の型に対して定義をしていくことが可能になってくるというものなので、もう少し前半に出ていても良かったのかなと思いました。ですが、次の章でオブジェクト指向について考えることになるので、オブジェクト指向の前にトレイトを見て、オブジェクト指向言語ではない Rust に存在しているオブジェクト指向のような捉え方ということの認識が深まればよいのかな、と改めて思いました。そんな考えも踏まえて、改めて公式ドキュメントのトレイトに関して共有する振る舞いということについて考えてみると、いろいろと理解が深まりました。
以下の内容が今日復習したことです。
-
第19章 トレイトを使う
- トレイトを使えば、ジェネリック関数の呼び出しに関するコンパイラのエラーメッセージが読みやすくなる>こと
- ジェネリックパラメータの制約が 1 つにまとまったり、複数のトレイトに分かれたりする理由
- ジェネリックパラメータに制約がないと、できることがどれほど少ないか
- トレイトに入れた関数のスコープ
- 複数のメソッドを含むトレイトを作る方法
- 複数のトレイトに渡すジェネリックパラメータを制約する方法
- トレイト継承を使う方法
- トレイトを使って外部型にメソッドを追加する方法
- 標準トレイトの
Display
とDebug
を実装する方法 - 便利なジェネリックトレイトを宣言する方法
- 型指定を持たないトレイトをシンプルに使える関連型
- イテレータの実装
Discussion