前の記事
- 【0】 準備 ← 初回
- ...
- 【20】 動的配列のリサイズ・イテレータ ~またまたトレイト登場!~ ← 前回
- 【21】 イテレータ・ライフタイム ~ライフタイム注釈登場!~ ← 今回
全記事一覧
- 【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 演習第21回になります!
今回の関連ページ
[06_ticket_management/05_iter] イテレータ 2 ( Iterator
トレイト? )
問題はこちらです。
use ticket_fields::{TicketDescription, TicketTitle};
// TODO: Provide an `iter` method that returns an iterator over `&Ticket` items.
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ticket {
title: TicketTitle,
description: TicketDescription,
status: Status,
}
#[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 super::*;
use ticket_fields::test_helpers::{ticket_description, ticket_title};
#[test]
fn add_ticket() {
let mut store = TicketStore::new();
let ticket = Ticket {
title: ticket_title(),
description: ticket_description(),
status: Status::ToDo,
};
store.add_ticket(ticket);
let ticket = Ticket {
title: ticket_title(),
description: ticket_description(),
status: Status::InProgress,
};
store.add_ticket(ticket);
let tickets: Vec<&Ticket> = store.iter().collect();
let tickets2: Vec<&Ticket> = store.iter().collect();
assert_eq!(tickets, tickets2);
}
}
解説
いつもなら「〇〇トレイトを実装しよう!」という流れになるところですが、今回は珍しく「トレイトではなく、本体が所有する iter
メソッドを定義しましょう!」という問題になっています。
まぁ今回もトレイトが全く関わっていないわけではなく、「返り値の型が Iterator
トレイトを実装している」という特徴があり、特に次回以降解決する部分のようです(コメントアウトしている関数のシグネチャがその伏線)。
impl TicketStore {
// ...省略...
// pub fn iter(&self) -> impl Iterator<Item = &Ticket> {
pub fn iter(&self) -> std::slice::Iter<'_, Ticket> {
self.tickets.iter()
}
}
Book の方に書かれている内容ですが、要は今回の目的は「所有権を奪わず、アイテムの 参照 をイテレートしたい」というものです。そのための手段が2つ紹介されています。
-
TicketStore
ではなくその参照&TicketStore
の方にIntoIterator
を実装する - 参照を要素とするイテレータを返す
今回は2つ目のアプローチというわけで、そしてイテレータであることを表す Iterator
トレイトを実装するのではなく、「そういう(参照の)イテレータを返す」だけですから、別にこの目的専用のトレイトはないのでしょう。慣例的に .iter()
メソッドとしています。
今回の問題では、内部で所有している動的配列である tickets
にある スライスのメソッド .iter()
の返り値をそのまま返しています。 Vec
の Deref
先がスライスなのでこの呼び出しが可能です。以上より、スライスの .iter()
の返り値型 std::slice::Iter<'a, T>
がそのまま TicketStore::iter
の返り値型になっています。
'_
や 'a
という表記方法がこの章まで来て(ようやく)出てきました。そう、これがライフタイム注釈というもので、次のエクササイズの話題のようです。
[06_ticket_management/06_lifetimes] ライフタイム注釈
問題はこちらです。
use ticket_fields::{TicketDescription, TicketTitle};
// TODO: Implement the `IntoIterator` trait for `&TicketStore` so that the test compiles and passes.
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
// ...省略...
pub fn iter(&self) -> std::slice::Iter<Ticket> {
self.tickets.iter()
}
}
テストを含めた全体
use ticket_fields::{TicketDescription, TicketTitle};
// TODO: Implement the `IntoIterator` trait for `&TicketStore` so that the test compiles and passes.
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[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);
}
pub fn iter(&self) -> std::slice::Iter<Ticket> {
self.tickets.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ticket_fields::test_helpers::{ticket_description, ticket_title};
#[test]
fn add_ticket() {
let mut store = TicketStore::new();
let ticket = Ticket {
title: ticket_title(),
description: ticket_description(),
status: Status::ToDo,
};
store.add_ticket(ticket);
let ticket = Ticket {
title: ticket_title(),
description: ticket_description(),
status: Status::InProgress,
};
store.add_ticket(ticket);
let tickets: Vec<&Ticket> = store.iter().collect();
let tickets2: Vec<&Ticket> = (&store).into_iter().collect();
assert_eq!(tickets, tickets2);
}
}
解説
前エクササイズで取り組まなかった &TicketStore
の方に Iterator
を実装する問題です。先のエクササイズでは '_
としてとりあえず考慮しなくて?良かったのですが、今回は Lifetime elision 、つまりライフタイム省略が効かないパターンらしく、考慮する必要があるみたいです。
impl<'a> IntoIterator for &'a TicketStore {
type Item = &'a Ticket;
type IntoIter = std::slice::Iter<'a, Ticket>;
fn into_iter(self) -> Self::IntoIter {
self.tickets.iter()
}
}
ライフタイム自体は 第8回 や 第12回 にて軽く関連した話が出てきていましたが、表記という形でちゃんと向き合うのは今回が初めてですね。
本問題での 'a
の使われ方はおおよそ次のような感じです(うーん間違ってるかも...)。
-
impl<'a>
の'a
:'a
というライフタイムパラメータを使いますよという宣言。ジェネリクスの型パラメータと同じと考えるとわかりやすい -
&'a TicketStore
,&'a Ticket
の'a
: ライフタイム'a
の間は生きていることが保証された参照、の意味 -
std::slice::Iter<'a, Ticket>
の'a
: ライフタイム'a
の間だけ生きている構造体
こんな後半になってようやくライフタイムが登場していることについて、筆者の直感と100 Exercisesが一致しているからかなぁとか思ったりします。
というのも、正直Rustの習得において ライフタイムと常に向き合う必要はありません 。Rust言語自体の仕様策定に関わりたいとかならともかく、日々のコーディングではコンパイラに怒られた時だけ考えれば良いです。ですから筆者もライフタイムは曖昧にしか理解していません。
ライフタイムで何かつまずいたら、次の順番で解決を試みるのがお勧めです。
-
Clone
トレイトを実装しちゃダメなのか検討する - もし効率面の問題ではなく借用等の問題から
Clone
トレイトを使えない、みたいな場合は、おそらく所有権の所在が不明である等が原因として考えられるので、Rc
やArc
が使えないかを検討する - もし効率面からなんとしても参照のまま複雑な構造体を扱いたい、という段になったら、ライフタイム注釈をつける
ライフタイム注釈を真正面から向き合わなくてもなんとかなるための考えや道具がRustにはいくつかあるわけです。今回は珍しく必要なシーン(実体ではなく参照を返したい)だったからようやく登場したという感じなのだと思います。普段は忘れていて大丈夫です!
ライフタイムに依存した構造体
おそらく Iter<'a, T>
は要素実体への参照を保持していると考えられます。しかし名前には &
が含まれず、「ライフタイム 'a
だけ存在していられる」以上の情報は含まれていないです。どうやったらこんな感じの構造体が書けるのでしょうか...?フィールドに参照があれば良い...?難しくはないですが、軽く確認してみたいと思います。こんな感じでしょうかね。
#[derive(Debug)]
struct Hoge<'a, T> {
my_slice: &'a [T],
}
fn main() {
let h: Hoge<'_, u32>;
{
let v = vec![1, 2, 3, 4, 5];
h = Hoge {
my_slice: v.as_slice(),
};
println!("{:?}", h);
}
// println!("{:?}", h); // ここで参照するとコンパイルエラー!
}
ついでに PhantomData
(幽霊型)という構造体を使って、ちょっと遊んでみました。表記上は参照を含みますが、この構造体はなんと実質 なんのデータも持たない のに、 ライフタイムパラメータだけはバッチリ有効 という存在になっています!
use std::marker::PhantomData;
struct Hoge;
#[derive(Debug)]
struct ResourceRelatedToHoge<'a>(PhantomData<&'a Hoge>);
impl<'a> ResourceRelatedToHoge<'a> {
fn new(_: &'a Hoge) -> Self {
Self(PhantomData)
}
}
fn main() {
let r: ResourceRelatedToHoge<'_>;
{
let hoge = Hoge;
r = ResourceRelatedToHoge::new(&hoge);
println!("{:?}", r);
}
// println!("{:?}", r); // ここで参照するとコンパイルエラー!
}
Rustで変わったことをしたい時(例えば筆者が実際に経験した例だと、RAII的に管理しなければならないWin32APIの某を扱う時など)に、「参照は要らないけど他の構造体に依存したい」という時は上記のように PhantomData
型を使うといい感じにできます!小ネタでした。
では次の問題に行きましょう!
次の記事: 【22】 コンビネータ・RPIT ~ \U0001F409 「 Iterator
トレイトを実装してるやつ」~