1
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Rust 100 Ex 🏃【25/37】 インデックス・可変インデックス ~インデックスもトレイト!~

Last updated at Posted at 2024-07-17

前の記事

全記事一覧

100 Exercise To Learn Rust 演習第25回になります!

今回の関連ページ

[06_ticket_management/13_index] インデックスアクセス

問題はこちらです。

lib.rs
// 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 に実装せよ!という問題ですね。

テスト中に、配列ではなく構造体に対して配列のようにアクセスしている

Rust
let ticket1 = &store[id1];

みたいな記述があります。すなわち今回扱う Index トレイトは [] による要素アクセスを実現するものというわけです1

解説

素直に実装してしまえばよいです。Bookで言及されているように、 Index トレイトは [] の中に入ってくる型(問題だと TicketId または &TicketId )を Index<Idx>Idx としてジェネリクスパラメータで、得られる参照の要素の型(問題だと Ticket)を Output 関連型で指定するようになっています。 Idx が決まれば Output も決まる、という写像のような関係になっていますね。

lib.rs
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] 可変インデックスアクセス

問題として用意されているソースコードはほとんど変わりません。可変インデックスに関するテストが追加されたのみです。

問題ソースコード全体
lib.rs
// 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);
    }
}

解説

問題がほとんど変わらないということは、つまり回答もほぼ変わらないということですね!シンプルに実装しています。

lib.rs
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の辞書型~

  1. どうでも良いですが、Bookの方で "How can we provide the same experience for TicketStore? You guessed right: we need to implement a trait, Index!" (どうやって配列アクセスを実現しましょうか...?あなたの予想は正しいです: トレイト Index を実装します!) という感じで「またトレイトか...」みたいな感じで書かれているの面白いですね。アレもトレイト、コレもトレイト、配列アクセスも演算子オーバーロードなわけでトレイトです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0