前の記事
- 【0】 準備 ← 初回
- ...
- 【24】 可変スライス・下書き構造体 ~構造体で状態表現~ ← 前回
- 【25】 インデックス・可変インデックス ~インデックスもトレイト!~ ← 今回
全記事一覧
- 【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 演習第25回になります!
今回の関連ページ
[06_ticket_management/13_index] インデックスアクセス
問題はこちらです。
// TODO: Implement `Index<&TicketId>` and `Index<TicketId>` for `TicketStore`.
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
counter: u64,
}
#[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(),
counter: 0,
}
}
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
let id = TicketId(self.counter);
self.counter += 1;
let ticket = Ticket {
id,
title: ticket.title,
description: ticket.description,
status: Status::ToDo,
};
self.tickets.push(ticket);
id
}
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
self.tickets.iter().find(|&t| t.id == id)
}
}
#[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[id1];
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[&id2];
assert_ne!(id1, id2);
}
}
Index<TicketId>
トレイトと、 Index<&TicketId>
トレイトを TicketStore
に実装せよ!という問題ですね。
テスト中に、配列ではなく構造体に対して配列のようにアクセスしている
let ticket1 = &store[id1];
みたいな記述があります。すなわち今回扱う Index
トレイトは []
による要素アクセスを実現するものというわけです1!
解説
素直に実装してしまえばよいです。Bookで言及されているように、 Index
トレイトは []
の中に入ってくる型(問題だと TicketId
または &TicketId
)を Index<Idx>
の Idx
としてジェネリクスパラメータで、得られる参照の要素の型(問題だと Ticket
)を Output
関連型で指定するようになっています。 Idx
が決まれば Output
も決まる、という写像のような関係になっていますね。
use std::ops::Index;
impl Index<TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, index: TicketId) -> &Self::Output {
self.get(index).unwrap()
}
}
impl Index<&TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, index: &TicketId) -> &Self::Output {
self.get(*index).unwrap()
}
}
get
メソッドは範囲外アクセスの時は None
を返してくれますが、 index
メソッドでは折角返してくれた Option
型を unwrap
しています。Rustでは []
による要素アクセスは範囲外の時はパニックするというのが慣例なので問題ないです。 Option
を返すようにしてほしい時は、スライスのメソッド get
と同様に get
メソッドとして提供するのが慣例です。
[06_ticket_management/14_index_mut] 可変インデックスアクセス
問題として用意されているソースコードはほとんど変わりません。可変インデックスに関するテストが追加されたのみです。
問題ソースコード全体
// TODO: Implement `IndexMut<&TicketId>` and `IndexMut<TicketId>` for `TicketStore`.
use std::ops::Index;
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
counter: u64,
}
#[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(),
counter: 0,
}
}
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
let id = TicketId(self.counter);
self.counter += 1;
let ticket = Ticket {
id,
title: ticket.title,
description: ticket.description,
status: Status::ToDo,
};
self.tickets.push(ticket);
id
}
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
self.tickets.iter().find(|&t| t.id == id)
}
}
impl Index<TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, index: TicketId) -> &Self::Output {
self.get(index).unwrap()
}
}
impl Index<&TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, index: &TicketId) -> &Self::Output {
&self[*index]
}
}
#[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 draft = TicketDraft {
title: ticket_title(),
description: ticket_description(),
};
let id = store.add_ticket(draft.clone());
let ticket = &store[id];
assert_eq!(draft.title, ticket.title);
assert_eq!(draft.description, ticket.description);
assert_eq!(ticket.status, Status::ToDo);
let ticket = &mut store[id];
ticket.status = Status::InProgress;
let ticket = &store[id];
assert_eq!(ticket.status, Status::InProgress);
}
}
解説
問題がほとんど変わらないということは、つまり回答もほぼ変わらないということですね!シンプルに実装しています。
use std::ops::IndexMut;
impl IndexMut<TicketId> for TicketStore {
// <TicketStore as Index>::Outputは一意に決まるからSelf::Outputで取得できる
fn index_mut(&mut self, index: TicketId) -> &mut Self::Output {
self.tickets.iter_mut().find(|t| t.id == index).unwrap()
}
}
impl IndexMut<&TicketId> for TicketStore {
fn index_mut(&mut self, index: &TicketId) -> &mut Self::Output {
&mut self[*index]
}
}
なお、関連型の Output
の指定がありません。これは IndexMut<Idx>
がトレイト境界に Index<Idx>
を要求しているからで、 Self::Output
の型は Index<Idx>
で指定した型と同一のものが入っています!
では次の問題に行きましょう!
次の記事: 【26】 HashMap・順序・BTreeMap ~Rustの辞書型~