前の記事
- 【0】 準備 ← 初回
- ...
- 【8】 デストラクタ(変数の終わり)・トレイト ~終わりと始まり~ ← 前回
- 【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ~ Empowerment 💪 ~ ← 今回
全記事一覧
- 【0】 準備
- 【1】 構文・整数・変数
- 【2】 if・パニック・演習
- 【3】 可変・ループ・オーバーフロー
- 【4】 キャスト・構造体 (たまにUFCS)
- 【5】 バリデーション・モジュールの公開範囲 ~ → カプセル化!~
- 【6】 カプセル化の続きと所有権とセッター ~そして不変参照と可変参照!~
- 【7】 スタック・ヒープと参照のサイズ ~メモリの話~
- 【8】 デストラクタ(変数の終わり)・トレイト ~終わりと始まり~
- 【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ~Empowerment 💪 ~
- 【10】 トレイト境界・文字列・Derefトレイト ~トレイトのアレコレ~
- 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~
- 【12】 Clone・Copy・Dropトレイト ~覚えるべき主要トレイトたち~
- 【13】 トレイトまとめ・列挙型・match式 ~最強のトレイトの次は、最強の列挙型~
- 【14】 フィールド付き列挙型とOption型 ~チョクワガタ~
- 【15】 Result型 ~Rust流エラーハンドリング術~
- 【16】 Errorトレイトと外部クレート ~依存はCargo.tomlに全部お任せ!~
- 【17】 thiserror・TryFrom ~トレイトもResultも自由自在!~
- 【18】 Errorのネスト・慣例的な書き方 ~Rustらしさの目醒め~
- 【19】 配列・動的配列 ~スタックが使われる配列と、ヒープに保存できる動的配列~
- 【20】 動的配列のリサイズ・イテレータ ~またまたトレイト登場!~
- 【21】 イテレータ・ライフタイム ~ライフタイム注釈ようやく登場!~
- 【22】 コンビネータ・RPIT ~ 「
Iterator
トレイトを実装してるやつ」~ - 【23】
impl Trait
・スライス ~配列の欠片~ - 【24】 可変スライス・下書き構造体 ~構造体で状態表現~
- 【25】 インデックス・可変インデックス ~インデックスもトレイト!~
- 【26】 HashMap・順序・BTreeMap ~Rustの辞書型~
- 【27】 スレッド・'staticライフタイム ~並列処理に見るRustの恩恵~
- 【28】 リーク・スコープ付きスレッド ~ライフタイムに技あり!~
- 【29】 チャネル・参照の内部可変性 ~Rustの虎の子、mpscと
Rc<RefCell<T>>
~ - 【30】 双方向通信・リファクタリング ~返信用封筒を入れよう!~
- 【31】 上限付きチャネル・PATCH機能 ~パンクしないように制御!~
- 【32】
Send
・排他的ロック(Mutex
)・非対称排他的ロック(RwLock
) ~真打Arc<Mutex<T>>
登場~ - 【33】 チャネルなしで実装・Syncの話 ~考察回です~
- 【34】
async fn
・非同期タスク生成 ~Rustの非同期入門~ - 【35】 非同期ランタイム・Futureトレイト ~非同期のお作法~
- 【36】 ブロッキング・非同期用の実装・キャンセル ~ラストスパート!~
- 【37】 Axumでクラサバ! ~最終回~
- 【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】
- 【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か
100 Exercise To Learn Rust 演習第9回になります!
今回の関連ページ
[04_traits/02_orphan_rule] Orphan Rule (孤児ルール)
問題はこちらです。
// TODO: this is an example of an orphan rule violation.
// We're implementing a foreign trait (`PartialEq`, from `std`) on
// a foreign type (`u32`, from `std`).
// Look at the compiler error to get familiar with what it looks like.
// Then delete the code below and move on to the next exercise.
impl PartialEq for u32 {
fn eq(&self, _other: &Self) -> bool {
todo!()
}
}
"Orphan Rule" (訳すなら孤児ルール...?あるいは Coherence (一貫性) とも呼ばれてる) の制約上、上記のソースコードはコンパイルが通らないことを確認して次に進めとのことらしいです。
解説
"Orphan Rule" は次のようなルールです。
Things get more nuanced when multiple crates are involved. In particular, at least one of the following must be true:
- The trait is defined in the current crate
- The implementor type is defined in the current crate
This is known as Rust's orphan rule. Its goal is to make the method resolution process unambiguous.
わかりやすさのために以下の例を考えます。
struct Hoge;
trait Fuga {}
impl Fuga for Hoge {}
上記のコードにおいて、次のうちどちらかを満たしていなければならない:
- トレイト(
Fuga
)が自前のクレートで定義されたものである - 構造体(
Hoge
)が自前のクレートで定義されたものである
上記コードそのままは両方を満たしているため問題ありません。
一方、問題のコードは PartialEq
トレイトも u32
型も自分で定義したものではありません。そのためエラーになります。例えるなら、「他人の褌で相撲は取れない」みたいな感覚でしょうか...?
そして以下はOrphan Ruleを回避できている回答例です。既存の型に対して、「ニュータイプパターン」という技法を用いる(単に対象の型をラップしてるだけ)ことで、「構造体を自前で用意した」ことにして制約を回避しています(後のエクササイズで登場してたかな...?)。
#[derive(Clone, Copy, Debug)]
pub struct MyU32(pub u32);
impl PartialEq for MyU32 {
fn eq(&self, other: &Self) -> bool {
let res = self.0 == other.0;
println!("Eq method called. {:?} == {:?} is {}", self, other, res);
res
}
}
[04_traits/03_operator_overloading] 演算子オーバーロード
問題はこちらです。
use std::cmp::PartialEq;
struct Ticket {
title: String,
description: String,
status: String,
}
// TODO: Implement the `PartialEq` trait for `Ticket`.
impl PartialEq for Ticket {}
テストを含めた全体
use std::cmp::PartialEq;
struct Ticket {
title: String,
description: String,
status: String,
}
// TODO: Implement the `PartialEq` trait for `Ticket`.
impl PartialEq for Ticket {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_eq() {
let title = "title";
let description = "description";
let status = "To-Do";
let ticket1 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: status.to_string(),
};
assert!(ticket1 == ticket2);
}
#[test]
fn test_description_not_matching() {
let title = "title";
let status = "To-Do";
let ticket1 = Ticket {
title: title.to_string(),
description: "description".to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: "description2".to_string(),
status: status.to_string(),
};
assert!(ticket1 != ticket2);
}
#[test]
fn test_title_not_matching() {
let status = "To-Do";
let description = "description";
let ticket1 = Ticket {
title: "title".to_string(),
description: description.to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: "title2".to_string(),
description: description.to_string(),
status: status.to_string(),
};
assert!(ticket1 != ticket2);
}
#[test]
fn test_status_not_matching() {
let title = "title";
let description = "description";
let ticket1 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: "status".to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: "status2".to_string(),
};
assert!(ticket1 != ticket2);
}
}
PartialEq
を Ticket
に実装してほしいという問題です!
解説
PartialEq
を型に実装することで、その型において ==
演算子による比較が可能になります!(実装されていない場合勝手に参照同士が比較される、なんてヘンテコなことは起きません!) すなわちいわゆる「演算子オーバーロード」は、Rustではトレイトを実装するという形で実現できるわけです。
String
型にはあらかじめ PartialEq
が実装されているので、文字列同士を ==
で比較して、その結果(真偽値)を &&
で結ぶことで目的は達成されます。
impl PartialEq for Ticket {
fn eq(&self, other: &Self) -> bool {
self.title == other.title
&& self.description == other.description
&& self.status == other.status
}
}
ところで、上記の回答だと、 「全フィールド同士で比較したかどうかわからない」という問題があります。言い換えると、「Ticket
構造体に変更が加わった際、 PartialEq
にも改変を加えなければならないのに、そのことに気付けない」という問題があります。 Ticket
を改変した時に PartialEq
の実装コードの方でも「比較が足りないよ」ということでエラーになってくれると便利です。
こういう時に便利な書き方が、次のエクササイズの冒頭で紹介されている「分解構文」(Destructing syntax)です。
impl PartialEq for Ticket {
fn eq(&self, other: &Self) -> bool {
let Ticket {
title: self_title,
description: self_description,
status: self_status,
} = self;
let Ticket {
title: other_title,
description: other_description,
status: other_status,
} = other;
self_title == other_title
&& self_description == other_description
&& self_status == other_status
}
}
これは「一意なパターンしか取れない(論駁不可能な)パターンマッチング」とも言える機能で、後ほどのエクササイズ(第13回・第14回)にて登場する match
式や if let
式に近い機能なのですが、それはさておき、もし書き出しているフィールドに不足があればコンパイルエラーになってくれたりします。筆者はこの分解構文が結構好きで多用しがちです。(最近乱用気味かも...)
ただ今回は(今回はというよりは、この目的に対しては)冗長な書き方に見えます。そのため本エクササイズの回答ではここまではしていませんでしたし、もっと適切な書き方こそ次のエクササイズで取り組む内容です。
[04_traits/04_derive] deriveマクロ
問題はこちらです。
// TODO: A (derivable) trait implementation is missing for this exercise to compile successfully.
// Fix it!
//
// # `Debug` primer
//
// `Debug` returns a representation of a Rust type that's suitable for debugging (hence the name).
// `assert_eq!` requires `Ticket` to implement `Debug` because, when the assertion fails, it tries to
// print both sides of the comparison to the terminal.
// If the compared type doesn't implement `Debug`, it doesn't know how to represent them!
#[derive(PartialEq)]
struct Ticket {
title: String,
description: String,
status: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_eq() {
// 省略
// 例えば以下の行でコンパイルエラーとなる
assert_eq!(ticket1, ticket2);
}
// 省略
}
テストを含めた全体
// TODO: A (derivable) trait implementation is missing for this exercise to compile successfully.
// Fix it!
//
// # `Debug` primer
//
// `Debug` returns a representation of a Rust type that's suitable for debugging (hence the name).
// `assert_eq!` requires `Ticket` to implement `Debug` because, when the assertion fails, it tries to
// print both sides of the comparison to the terminal.
// If the compared type doesn't implement `Debug`, it doesn't know how to represent them!
#[derive(PartialEq)]
struct Ticket {
title: String,
description: String,
status: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_eq() {
let title = "title";
let description = "description";
let status = "To-Do";
let ticket1 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: status.to_string(),
};
assert_eq!(ticket1, ticket2);
}
#[test]
fn test_description_not_matching() {
let title = "title";
let status = "To-Do";
let ticket1 = Ticket {
title: title.to_string(),
description: "description".to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: "description2".to_string(),
status: status.to_string(),
};
assert_ne!(ticket1, ticket2);
}
#[test]
fn test_title_not_matching() {
let status = "To-Do";
let description = "description";
let ticket1 = Ticket {
title: "title".to_string(),
description: description.to_string(),
status: status.to_string(),
};
let ticket2 = Ticket {
title: "title2".to_string(),
description: description.to_string(),
status: status.to_string(),
};
assert_ne!(ticket1, ticket2);
}
#[test]
fn test_status_not_matching() {
let title = "title";
let description = "description";
let ticket1 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: "status".to_string(),
};
let ticket2 = Ticket {
title: title.to_string(),
description: description.to_string(),
status: "status2".to_string(),
};
assert_ne!(ticket1, ticket2);
}
}
Bookの序盤の方で「 PartialEq
」をDeriveマクロで実装する話が出ています。これこそが「もっと適した全フィールド比較の書き方」というわけでした。
演習の方では PartialEq
は実装済みで、 「 Debug
マクロが足りてないから足してくれ」という問題になっています。
解説
今回から本格的にRustの「マクロ」が紹介されましたね。TRPLでは専用のページを割いて紹介されている要素ですが、100 Exercisesではメインで取り上げているのは今回ぐらいで、マクロ自体は簡単な紹介がなされているだけのようです。
まぁそうしているのもわかるというか、実は未だに筆者も「マクロの全部」は良くわかってません。宣言的マクロ( macro_rules!
を使って宣言するやつ)は別に難しいものではないので結構な頻度で使っていますが、手続き的マクロは実は定義したことがありません。
それでも、どっちかと言えば「使う」ことが多いから問題ないという感じでしょう。出てくるたびに覚えれば良くて、特に使っているだけのうちは「習うより慣れろ」色が強いのがRustのマクロだと思います。
今回の問題については、 Debug
トレイトにはDeriveマクロ(トレイトを実装するのが主な役割となる手続きマクロ)が用意されていることより、単に #[derive(...)]
に Debug
を加えてあげればよいです。
- #[derive(PartialEq)]
+ #[derive(PartialEq, Debug)]
struct Ticket {
title: String,
description: String,
status: String,
}
マクロがどのようにコードとして展開されるかは、 cargo expand
というツールを使うことで確認することが可能です。
Debug
マクロの該当箇所は次のような感じになっていました。
$ cargo expand
Checking derives v0.1.0 (/path/to/04_traits/04_derives)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
// ...省略...
#[automatically_derived]
impl ::core::fmt::Debug for Ticket {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(
f,
"Ticket",
"title",
&self.title,
"description",
&self.description,
"status",
&&self.status,
)
}
}
前回も話しましたが、演算子オーバーロードの例のように、Rustのヘンテコ機能はトレイトで提供されることが多いのです。そしてそのトレイトはDeriveマクロで手軽に構造体に与えられます。
TRPLの冒頭で、「RustはEmpowermentな言語」と紹介されていますが、特に今回のトレイトやDeriveマクロはそれを感じられる要素だったと思います。
では次の問題に行きましょう!