More than 1 year has passed since last update.
Rust 100 Ex 🏃【13/37】 トレイトまとめ・列挙型・match式 ~最強のトレイトの次は、最強の列挙型~
前の記事
- 【0】 準備 ← 初回
- ...
- 【12】 Clone・Copy・Dropトレイト ~覚えるべき主要トレイトたち~ ← 前回
- 【13】 トレイトまとめ・列挙型・match式 ~ 最強のトレイトの次は、最強の列挙型 ~ ← 今回
100 Exercise To Learn Rust 演習第13回になります!今回でRustのキー要素であるトレイトの話が終わり、お次はもう一つの強烈な機能である列挙型の話に入っていきます。
今回の関連ページ
[04_traits/14_outro] トレイト付与復習
問題指示はこちらになります。
- TODO:
SaturatingU16型を宣言してね!-
u16型の値を内包しています -
u16,u8,&u16,&u8から変換することが可能です -
SaturatingU16,u16,&u16,&SaturatingU16との加算ができます - 加算の上限は
u16を超えないようにしてください。(飽和加算) - その他の
SaturatingU16かu16型の変数と比較可能です - デバッグ出力を可能にしてください
-
- テストは
testsフォルダに有るよ!公開範囲(pubとか)に注意してね
解説
ここまでの復習ですね!実装あるのみです!
#[derive(Debug, Clone, Copy)]
pub struct SaturatingU16(u16);
macro_rules! impl_from {
($t:ty) => {
impl From<$t> for SaturatingU16 {
fn from(val: $t) -> Self {
Self(val.clone() as u16)
}
}
};
}
impl_from!(u16);
impl_from!(u8);
impl_from!(&u16);
impl_from!(&u8);
use std::ops::Add;
macro_rules! impl_add {
($t:ty) => {
impl Add<$t> for SaturatingU16 {
type Output = Self;
fn add(self, rhs: $t) -> Self::Output {
self.0
.saturating_add(SaturatingU16::from(rhs.clone()).0)
.into()
}
}
};
}
impl_add!(SaturatingU16);
impl_add!(&SaturatingU16);
impl_add!(u16);
impl_add!(&u16);
macro_rules! impl_eq {
($t:ty) => {
impl PartialEq<$t> for SaturatingU16 {
fn eq(&self, other: &$t) -> bool {
self.0 == SaturatingU16::from(other.clone()).0
}
}
};
}
impl_eq!(SaturatingU16);
impl Eq for SaturatingU16 {}
impl_eq!(u16);
トレイトとマクロで実装処理をまとめられると、「処理の全体像」が見えている感じがして爽快感があります!ありませんか...?
[05_ticket_v2/01_enum] 列挙型
本問題から5章です!問題はこちらです。
// TODO: use `Status` as type for `Ticket::status`
// Adjust the signature and implementation of all other methods as necessary.
#[derive(Debug, PartialEq)]
// `derive`s are recursive: it can only derive `PartialEq` if all fields also implement `PartialEq`.
// Same holds for `Debug`. Do what you must with `Status` to make this work.
struct Ticket {
title: String,
description: String,
status: String,
}
enum Status {
// TODO: add the missing variants
}
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
Ticket {
title,
description,
status,
}
}
pub fn title(&self) -> &String {
&self.title
}
pub fn description(&self) -> &String {
&self.description
}
pub fn status(&self) -> &String {
&self.status
}
}
本問題で、 第5回で後ほど改善したいと言っていた部分の一つが改善されます...! status フィールドでは ToDo 、 InProgress 、 Done の3つの値しか許したくないという要件がありました。
列挙型 (あるいは列挙体) Enum は、まさにこのような限定された状態しか取りたくない時に使用する機能です!
解説
#[derive(Debug, PartialEq)]
struct Ticket {
title: String,
description: String,
- status: String,
+ status: Status,
}
+ #[derive(Debug, PartialEq, Clone, Copy)]
enum Status {
+ ToDo,
+ InProgress,
+ Done,
}
impl Ticket {
- pub fn new(title: String, description: String, status: String) -> Ticket {
+ pub fn new(title: String, description: String, status: Status) -> Ticket {
// 他のバリデーション省略
- if status != "To-Do" && status != "In Progress" && status != "Done" {
- panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
- }
Ticket {
title,
description,
status,
}
}
// 他のゲッター省略
- pub fn status(&self) -> &String {
+ pub fn status(&self) -> &Status {
&self.status
}
}
以下に示す列挙型を導入することで、バリデーションを削除することが可能になりました!やったね!
enum Status {
ToDo,
InProgress,
Done,
}
String よりも列挙体の方がプログラムを書く際にもミスしにくいのは明らかでしょう(タイポとか気にしなくて良くなるなど)。今後は積極的に使っていきたい...どころか5章の主役となります。
というのも、Rustの列挙体にはたくさんの強力な機能があるからです...とりあえず、次の問題に進みましょう。
[05_ticket_v2/02_match] match式
問題はこちらです。
enum Shape {
Circle,
Square,
Rectangle,
Triangle,
Pentagon,
}
impl Shape {
// TODO: Implement the `n_sides` method using a `match`.
pub fn n_sides(&self) -> u8 {
todo!()
}
}
Shape 型変数 self の値はここでは Shape::Circle や Shape::Square という値として入ってきます。イメージとしては bool 型変数なら true や false が値として取りうる、というのが、列挙型だと定義した数分ある感じでしょうか...?まぁ他言語と似たようなものだと思います。
どうやら列挙体 Shape のバリアントによって、 n_sides (頂点数)メソッドが返す値を変えたいようです。
解説
「パターンマッチ」に従って条件分岐が可能な match "式"を利用して実装します。 if 式同様こちらも式のため、最後に評価してほしい値をぽんと置いている感じになっています。
impl Shape {
pub fn n_sides(&self) -> u8 {
// ここでuse文を使うことでちょっと楽できます
use Shape::*;
match self {
Circle => 0,
Square => 4,
Rectangle => 4,
Triangle => 3,
Pentagon => 5,
}
}
}
この時点で覚えておきたいことは、「 match 式は(というよりはパターンマッチは)網羅的」であることです。ためしに Pentagon を抜いてみたりすると怒られます。逆に言えばパターンが網羅されていればおkなので、なんと整数型などにも以下のようにして応用可能です!
use rand::prelude::*;
fn main() {
let mut rng = thread_rng();
let v: u8 = rng.gen();
match v {
0 => println!("ゼロ!すごい!"),
1..=127 => println!("前半"),
128..=255 => println!("後半"),
}
}
また、第9回で少しだけ触れてはいましたが、「パターンマッチ」という機能も初登場しています。Rustのパターンマッチはかなり強力な機能で、Rustに慣れれば慣れるほどその深みにハマっていくこと請け合いです!
では次の問題に行きましょう!
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
