前の記事
- 【0】 準備 ← 初回
- ...
-
【23】
impl Trait
・スライス ~配列の欠片~ ← 前回 - 【24】 可変スライス・下書き構造体 ~構造体で状態表現~ ← 今回
全記事一覧
- 【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 演習第24回になります!
今回の関連ページ
[06_ticket_management/11_mutable_slices] 可変スライス
問題はこちらです。
// TODO: Define a function named `lowercase` that converts all characters in a string to lowercase,
// modifying the input in place.
// Does it need to take a `&mut String`? Does a `&mut str` work? Why or why not?
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let mut s = String::from("");
lowercase(&mut s);
assert_eq!(s, "");
}
#[test]
fn one_char() {
let mut s = String::from("A");
lowercase(&mut s);
assert_eq!(s, "a");
}
#[test]
fn multiple_chars() {
let mut s = String::from("Hello, World!");
lowercase(&mut s);
assert_eq!(s, "hello, world!");
}
#[test]
fn mut_slice() {
let mut s = "Hello, World!".to_string();
lowercase(s.as_mut_str());
assert_eq!(s, "hello, world!");
}
}
コメントを要約するとこんな感じです。
- TODO: 全文字を小文字にする
lowercase
関数を定義してください- 入力自体を修飾します(つまり可変参照を受け取る)
-
&mut String
と&mut str
どちらで受け取ると良さそうでしょうか?その理由は?
理由まで聞かれているので解説では理由も回答したいなと思います。
解説
前回はスライス &[T]
が登場した回でした!不変参照については静的系([T;N]
, &'static str
)・ヒープ系(Vec<T>
, String
)問わず受け取れる &[T]
や &str
が良いとされてきましたが、では可変参照でもそうした方が良いのか?という問題で、 Book の方では「そうとは限らない」といった趣旨の説明がなされています。
本問題への回答は次のような感じです。 &mut str
、すなわち str
型を使用する方を選択しました。
fn lowercase(s: &mut str) {
// fn lowercase(s: &mut String) { // どっち?という問題でこちらは選ばなかった
s.make_ascii_lowercase();
}
以下、理由です。(作り込んだので堅苦しいです)
-
&mut str
はmake_ascii_lowercase
メソッドを持っているため、引数を&mut str
として受け取ることでも本処理を実現できます。(&mut str
を引数に取れる十分性の説明)- しかし、
&'static str
型として扱われる文字列リテラルは(少なくとも safe Rust では)可変を取れないため、&mut String
を受け取っておけばほとんどのケースでは困りません。
- しかし、
- 本エクササイズではテストケースにて
&mut str
を返すas_mut_str
メソッドが呼ばれているので、&mut str
を引数型として用いなければなりません。 (&mut str
を引数型として受け取らなければならないという必要性の説明)
ユースケースを考えれば &mut String
でも良いだろうけど、問題の答えとしては &mut str
として受け取る他ないという説明でした。まぁ &mut String
だと関数内で何されるかわからないから利用者目線だとできることが制限される &mut str
の方が嬉しいという考え方もありますが、関数を作るのも使うのも自分なら &mut String
で良いと思いますね。
[06_ticket_management/12_two_states] 下書き構造体(ステートで構造体を分ける)
問題はこちらです。
// TODO: Update `add_ticket`'s signature: it should take a `TicketDraft` as input
// and return a `TicketId` as output.
// Each ticket should have a unique id, generated by `TicketStore`.
// Feel free to modify `TicketStore` fields, if needed.
//
// You also need to add a `get` method that takes as input a `TicketId`
// and returns an `Option<&Ticket>`.
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TicketId(u64);
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub id: TicketId,
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TicketDraft {
pub title: TicketTitle,
pub description: TicketDescription,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
}
}
// ここに改変が必要
pub fn add_ticket(&mut self, ticket: Ticket) {
self.tickets.push(ticket);
}
}
#[cfg(test)]
mod tests {
use crate::{Status, TicketDraft, TicketStore};
use ticket_fields::test_helpers::{ticket_description, ticket_title};
#[test]
fn works() {
let mut store = TicketStore::new();
let draft1 = TicketDraft {
title: ticket_title(),
description: ticket_description(),
};
let id1 = store.add_ticket(draft1.clone());
let ticket1 = store.get(id1).unwrap();
assert_eq!(draft1.title, ticket1.title);
assert_eq!(draft1.description, ticket1.description);
assert_eq!(ticket1.status, Status::ToDo);
let draft2 = TicketDraft {
title: ticket_title(),
description: ticket_description(),
};
let id2 = store.add_ticket(draft2);
let ticket2 = store.get(id2).unwrap();
assert_ne!(id1, id2);
}
}
チケットマネージャを作るテーマに戻ってきました。 Vec<Ticket>
に新たなチケットを加える add_ticket
メソッドがありましたが、今回から Ticket
構造体それ自体ではなくその下書きである TicketDraft
構造体を引数として取り、IDを返すように改変し、ついでにストアからIDでチケットをオプショナルに取得する get
メソッドを定義せよ、という問題です。
解説
指示通り引数の型を Ticket
から、 id
と status
がオミットされている TicketDraft
に変更し、最後にIDを返すように改変すればおkです!
impl TicketStore {
// ...省略...
pub fn add_ticket(&mut self, ticket_draft: TicketDraft) -> TicketId {
let TicketDraft { title, description } = ticket_draft;
let id = TicketId(self.tickets.len() as _);
let ticket = Ticket {
title,
id,
description,
status: Status::ToDo,
};
self.tickets.push(ticket);
id
}
fn get(&self, id: TicketId) -> Option<&Ticket> {
self.tickets.iter().find(|t| t.id == id)
}
}
id
は、現在の動的配列のサイズを渡すことで作成することにしました。この方法なら衝突しません。TicketId
が u64
を要求する一方で、 .len()
は usize
型を返すのでキャストしています。(キャスト先の型が推論できる時は as _
と書くだけで良くて便利)
status
は作成時点では ToDo
と決め打ちしています。
get
メソッドでは、とりあえず今回は find
メソッドを使うことで最初に id
が一致したものを返すという風にしました。( O(N)
かかるのをなんとかするのがまさに次回以降の話題です!)
今回は目新しい話題や技術的に難しい内容はありませんでしたね。それはそれとしてBookの内容は今回も面白いです。
id
をNULLABLEにする代わりに、 Ticket
と TicketDraft
という形でステートで分けて管理することで不正なオブジェクト(生焼けオブジェクト)の生成を防ぐ、というのはシンプルですが中々興味深いです。最終的には省かれますが、まず Option
型にしなければ表現できない id
フィールドが発見され、そこから「生焼けオブジェクトを防ぐために構造体を分けたほうが良い」という気付きが得られるという流れは、NULL安全であることで初めて気づきやすくなる部分でしょう。
「ステートで構造体を分けてしまおう」というのは、クラスが無いため構造体の定義に対してフッ軽で、 Option
型があるからこそたどり着ける、ということで、Rustならではの考え方なんじゃないかと個人的には思います!
では次の問題に行きましょう!