前の記事
- 【0】 準備 ← 初回
- ...
- 【15】 Result型 ~Rust流エラーハンドリング術~ ← 前回
- 【16】 Errorトレイトと外部クレート ~ 依存はCargo.tomlに全部お任せ! ~ ← 今回
全記事一覧
- 【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 演習第16回になります!
今回の関連ページ
[05_ticket_v2/09_error_trait] Error トレイト
問題はこちらです。
// TODO: Implement `Debug`, `Display` and `Error` for the `TicketNewError` enum.
// When implementing `Display`, you may want to use the `write!` macro from Rust's standard library.
// The docs for the `std::fmt` module are a good place to start and look for examples:
// https://doc.rust-lang.org/std/fmt/index.html#write
enum TicketNewError {
TitleError(String),
DescriptionError(String),
}
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
// stored inside the relevant variant of the `TicketNewError` enum.
// When the description is invalid, instead, it should use a default description:
// "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
todo!()
}
#[derive(Debug, PartialEq, Clone)]
struct Ticket {
title: String,
description: String,
status: Status,
}
// ...省略...
全体
// TODO: Implement `Debug`, `Display` and `Error` for the `TicketNewError` enum.
// When implementing `Display`, you may want to use the `write!` macro from Rust's standard library.
// The docs for the `std::fmt` module are a good place to start and look for examples:
// https://doc.rust-lang.org/std/fmt/index.html#write
enum TicketNewError {
TitleError(String),
DescriptionError(String),
}
// TODO: `easy_ticket` should panic when the title is invalid, using the error message
// stored inside the relevant variant of the `TicketNewError` enum.
// When the description is invalid, instead, it should use a default description:
// "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
todo!()
}
#[derive(Debug, PartialEq, Clone)]
struct Ticket {
title: String,
description: String,
status: Status,
}
#[derive(Debug, PartialEq, Clone)]
enum Status {
ToDo,
InProgress { assigned_to: String },
Done,
}
impl Ticket {
pub fn new(
title: String,
description: String,
status: Status,
) -> Result<Ticket, TicketNewError> {
if title.is_empty() {
return Err(TicketNewError::TitleError(
"Title cannot be empty".to_string(),
));
}
if title.len() > 50 {
return Err(TicketNewError::TitleError(
"Title cannot be longer than 50 bytes".to_string(),
));
}
if description.is_empty() {
return Err(TicketNewError::DescriptionError(
"Description cannot be empty".to_string(),
));
}
if description.len() > 500 {
return Err(TicketNewError::DescriptionError(
"Description cannot be longer than 500 bytes".to_string(),
));
}
Ok(Ticket {
title,
description,
status,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use common::{overly_long_description, overly_long_title, valid_description, valid_title};
use static_assertions::assert_impl_one;
#[test]
#[should_panic(expected = "Title cannot be empty")]
fn title_cannot_be_empty() {
easy_ticket("".into(), valid_description(), Status::ToDo);
}
#[test]
fn template_description_is_used_if_empty() {
let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo);
assert_eq!(ticket.description, "Description not provided");
}
#[test]
#[should_panic(expected = "Title cannot be longer than 50 bytes")]
fn title_cannot_be_longer_than_fifty_chars() {
easy_ticket(overly_long_title(), valid_description(), Status::ToDo);
}
#[test]
fn template_description_is_used_if_too_long() {
let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo);
assert_eq!(ticket.description, "Description not provided");
}
#[test]
fn display_is_correctly_implemented() {
let ticket = Ticket::new("".into(), valid_description(), Status::ToDo);
assert_eq!(format!("{}", ticket.unwrap_err()), "Title cannot be empty");
}
assert_impl_one!(TicketNewError: std::error::Error);
}
今回の問題は正直あんまり"旨味"を感じにくい気がします。というのも、自前定義したエラー用の列挙体に Error
トレイトを実装する必要性が問題内にはないからです。
解説
Error
トレイトは Debug
トレイトと Display
トレイトを要求するので、これらを実装することで Error
トレイトを実装できます。
use std::error::Error;
use std::fmt;
#[derive(Debug)]
enum TicketNewError {
TitleError(String),
DescriptionError(String),
}
use TicketNewError::*;
impl fmt::Display for TicketNewError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (TitleError(s) | DescriptionError(s)) = self;
write!(f, "{}", s)
}
}
impl Error for TicketNewError {}
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
let ticket_w = Ticket::new(title.clone(), description, status.clone());
match ticket_w {
Ok(ticket) => ticket,
Err(DescriptionError(_)) => {
Ticket::new(title, "Description not provided".to_string(), status).unwrap()
}
t => t.unwrap(),
}
}
本問題は次問題以降の布石的な感じですね。用意したエラー型が std::error::Error
を実装してくれていたほうが、エラーレポートの観点で後々便利なので実装しておいた方が良い、というのは確かです...でもやっぱり今回みたいに丁寧に実装することはほぼないんだよなぁ...
[05_ticket_v2/10_packages] パッケージ (クレート群)
問題はこちらです。今回はめずらしく lib.rs
が準備されておらず、 lib.rs
を自分で用意して main.rs
で読み込むという構造になっています。
// This is a `main.rs` file, therefore `cargo` interprets this as the root of a binary target.
// TODO: fix this broken import. Create a new library target in the `src` directory.
// The library target should expose a public function named `hello_world` that takes no arguments
// and returns nothing.
use packages::hello_world;
// This is the entrypoint of the binary.
fn main() {
hello_world();
}
なぜ急に「外部のパッケージを読み込む」という問題を解いているかというと、最終的に thiserror
という便利パッケージを読み込むためですね。
解説
lib.rs
を用意して、必要な関数を書き pub
で公開します。
pub fn hello_world() {}
今回の問題は、 lib.rs
と main.rs
は別モジュール扱いされるという仕組みのお陰で成り立っている部分もありますね。 lib.rs
以下ではクレートのルートパスは crate
なのですが、 main.rs
から見た lib.rs
は Cargo.toml
の package.name
が参照されるというのは、覚えておくと便利でしょう。
[05_ticket_v2/11_dependencies] 外部パッケージの利用
問題はこちらです。「サードパーティ製のクレートを使ってみよう!」という問題です!
// TODO: Add `anyhow` as a dependency of this project.
// Don't touch this import!
// When you import a type (`Error`) from a dependency, the import path must start
// with the crate name (`anyhow`, in this case).
use anyhow::Error;
今回も例外的な問題で、編集対象は lib.rs
ではなく外部依存クレートを管理するところである Cargo.toml
になります!
[package]
name = "deps"
version = "0.1.0"
edition = "2021"
解説
[dependencies]
の項目に anyhow = "..."
を加えることで、サードパーティクレート anyhow
が使用可能になります!
[package]
name = "deps"
version = "0.1.0"
edition = "2021"
+ [dependencies]
+ anyhow = "1.0.86"
Cargo.toml
を直接編集するのではなく、 cargo add
コマンドの使用をおすすめします。というのも、最新版を参照してくれるように書き込んでくれるためです。
cargo add anyhow
VSCodeを使っているならば、 crates という拡張機能もお勧めです。 Cargo.toml
ファイルを開いてワイルドカードを用いて anyhow = "*"
のようにバージョンを記載し、横の✅️を押すと最新版をサジェストしてくれます。
ちなみにワイルドカードによるバージョン指定について、面倒ならばそれでも問題はないですが(その代わり Cargo.lock
もちゃんと管理する必要があります)、なるべく再利用性や再現性を高めたいならしっかり具体的に指定することをオススメします。指定しておかないと後で後悔しがち...
では次の問題に行きましょう!