More than 1 year has passed since last update.
Rust 100 Ex 🏃【17/37】 thiserror・TryFrom ~トレイトもResultも自由自在!~
前の記事
- 【0】 準備 ← 初回
- ...
- 【16】 Errorトレイトと外部クレート ~依存はCargo.tomlに全部お任せ!~ ← 前回
- 【17】 thiserror・TryFrom ~トレイトもResultも自由自在!~ ← 今回
100 Exercise To Learn Rust 演習第17回になります!今回から投稿マラソンの関係で問題数を3つから2つに減らします、ご了承ください。
今回の関連ページ
[05_ticket_v2/12_thiserror] 便利クレート thiserror
問題はこちらです。
// TODO: Implement the `Error` trait for `TicketNewError` using `thiserror`.
// We've changed the enum variants to be more specific, thus removing the need for storing
// a `String` field into each variant.
// You'll also have to add `thiserror` as a dependency in the `Cargo.toml` file.
enum TicketNewError {
TitleCannotBeEmpty,
TitleTooLong,
DescriptionCannotBeEmpty,
DescriptionTooLong,
}
#[derive(Debug, PartialEq, Clone)]
struct Ticket {
title: String,
description: String,
status: Status,
}
#[derive(Debug, PartialEq, Clone)]
enum Status {
ToDo,
InProgress { assigned_to: String },
Done,
}
// ...省略...
前回複数のエクササイズに跨いで準備してきましたが、今回ようやく thiserror クレートを導入します!
解説
何はともあれまずは cargo add コマンドです。
cargo add thiserror
これで dependencies に thiserror が加わります。
[package]
name = "thiserror_"
version = "0.1.0"
edition = "2021"
[dependencies]
+ thiserror = "1.0.61"
[dev-dependencies]
common = { path = "../../../helpers/common" }
(上記で dev-dependencies は、テストのみに必要な依存を書くところになります。)
thiserror クレートが提供するderiveマクロ Error が使用できるようになるので、これを利用して Error トレイトを実装します。
#[derive(thiserror::Error, Debug)]
enum TicketNewError {
#[error("Title cannot be empty")]
TitleCannotBeEmpty,
#[error("Title cannot be longer than 50 bytes")]
TitleTooLong,
#[error("Description cannot be empty")]
DescriptionCannotBeEmpty,
#[error("Description cannot be longer than 500 bytes")]
DescriptionTooLong,
}
これだけで エラー文付きで Error トレイトを実装 してくれます! Clone トレイトの時にもあったメリットですが、deriveマクロを使うことで、「楽にトレイトを実装できる」他、「宣言的に、漏れなく実装できる」というメリットがあります!
裏で何をしてくれているかを調べるためにここまでの演習がありましたが、意味がわかった今はマクロを使っていきたいです。
[05_ticket_v2/13_try_from] TryFrom トレイト
問題はこちらです。
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for `Status`.
// The parsing should be case-insensitive.
#[derive(Debug, PartialEq, Clone)]
enum Status {
ToDo,
InProgress,
Done,
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
#[test]
fn test_try_from_string() {
let status = Status::try_from("ToDO".to_string()).unwrap();
assert_eq!(status, Status::ToDo);
let status = Status::try_from("inproGress".to_string()).unwrap();
assert_eq!(status, Status::InProgress);
let status = Status::try_from("Done".to_string()).unwrap();
assert_eq!(status, Status::Done);
}
#[test]
fn test_try_from_str() {
let status = Status::try_from("todo").unwrap();
assert_eq!(status, Status::ToDo);
let status = Status::try_from("inprogress").unwrap();
assert_eq!(status, Status::InProgress);
let status = Status::try_from("done").unwrap();
assert_eq!(status, Status::Done);
}
}
解説
TryFrom/TryInto は、キャスト用トレイト From/Into の失敗する可能性がある版です!関連型としてエラーの時に返す型を指定し、対応する Result 型を返す処理を実装します。
use Status::*;
fn str2status(value: &str) -> Result<Status, String> {
match value.to_lowercase().as_str() {
"todo" => Ok(ToDo),
"inprogress" => Ok(InProgress),
"done" => Ok(Done),
s => Err(s.to_string()),
}
}
impl TryFrom<&str> for Status {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
str2status(value)
}
}
impl TryFrom<String> for Status {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
str2status(&value)
}
}
String から Status への変換は今までのエクササイズでも似たような処理がしばしば登場していましたが、
- 列挙型の使用
-
TryFromによる実装
によってかなり慣例的で読みやすい形となりました!たとえば String::from("ToDo").try_into()? と書いてあれば、他のプログラマはこの部分を見ただけで「ここは文字列からの失敗する可能性のあるキャストなんだな」とわかります。
では次の問題に行きましょう!
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
