前の記事
- 【0】 準備 ← 初回
- ...
-
【22】 コンビネータ・RPIT ~ 「
Iterator
トレイトを実装してるやつ」~ ← 前回 - 【23】
impl Trait
・スライス ~配列の欠片~ ← 今回
全記事一覧
- 【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 演習第23回になります!
今回の関連ページ
[06_ticket_management/09_impl_trait_2] impl Trait
2 (引数位置の impl Trait
)
問題はこちらです。
// TODO: Rework the signature of `TicketStore::add_ticket` to use a generic type parameter rather
// than `impl Trait` syntax.
use ticket_fields::{TicketDescription, TicketTitle};
#[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(),
}
}
// Using `Into<Ticket>` as the type parameter for `ticket` allows the method to accept any type
// that can be infallibly converted into a `Ticket`.
// This can make it nicer to use the method, as it removes the syntax noise of `.into()`
// from the calling site. It can worsen the quality of the compiler error messages, though.
pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) {
self.tickets.push(ticket.into());
}
}
テストを含めた全体
// TODO: Rework the signature of `TicketStore::add_ticket` to use a generic type parameter rather
// than `impl Trait` syntax.
use ticket_fields::{TicketDescription, TicketTitle};
#[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(),
}
}
// Using `Into<Ticket>` as the type parameter for `ticket` allows the method to accept any type
// that can be infallibly converted into a `Ticket`.
// This can make it nicer to use the method, as it removes the syntax noise of `.into()`
// from the calling site. It can worsen the quality of the compiler error messages, though.
pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) {
self.tickets.push(ticket.into());
}
}
#[cfg(test)]
mod tests {
use super::*;
use ticket_fields::test_helpers::{ticket_description, ticket_title};
struct TicketDraft {
pub title: TicketTitle,
pub description: TicketDescription,
}
impl From<TicketDraft> for Ticket {
fn from(draft: TicketDraft) -> Self {
Self {
title: draft.title,
description: draft.description,
status: Status::ToDo,
}
}
}
#[test]
fn generic_add() {
let mut store = TicketStore::new();
// This won't compile if `add_ticket` uses `impl Trait` syntax in argument position.
store.add_ticket::<TicketDraft>(TicketDraft {
title: ticket_title(),
description: ticket_description(),
});
}
}
前回RPITとして熱く語った impl Trait
という表現を、ジェネリクスに直してください、という問題です!
解説
引数位置にある impl Trait
はRPITではなく、どちらかというと(ほぼ)ジェネリクスと同じ意味を持ちます。よって、ジェネリクスで書いてからトレイト境界を書けばよいです!
impl TicketStore {
pub fn add_ticket<T>(&mut self, ticket: T)
where
T: Into<Ticket>,
{
self.tickets.push(ticket.into());
}
}
今回は丁寧に where
を使ってみました。 Book の方にも書いていますが、ジェネリクスと「ほぼ」同じ意味とした「ほぼ」の部分の違いについて、 impl Trait
だと例えばターボフィッシュ構文 ( ::<T>
)による型指定ができません 。ターボフィッシュ構文というのは、呼び出される関数が曖昧になってしまわないように、ジェネリックスパラメータを具体的に指定するための構文です1。本問題はそれを確認する問題でした!
ちなみに本問題はテストのターボフィッシュ構文側を単に取り除いてしまえばコンパイルが通ります(テストを書き換えるのは回答としてはダメですが、つまりターボフィッシュの必要性を説明できていないということです)...ターボフィッシュ構文じゃないと辛くなる例を見つけようと色々試していたのですが、ターボフィッシュの問題以前にジェネリクス引数の方が表現力が強く、引数位置 impl Trait
だとそもそもいい感じに書けない場合が多そうでした。例えば、引数と返り値で同じ型にしたければジェネリック引数 T
を使う他ありません。
もし引数位置 impl Trait
で書ける処理ならば多分特に困らないです。ジェネリックパラメータをタイプするのすら面倒な時にはそこそこ便利ですので、使ってもいいんじゃないかと個人的には思います。
[06_ticket_management/10_slices] スライス
問題はこちらです。
// TODO: Define a function named `sum` that takes a reference to a slice of `u32` and returns the sum of all
// elements in the slice.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let v = vec![];
assert_eq!(sum(&v), 0);
}
#[test]
fn one_element() {
let v = vec![1];
assert_eq!(sum(&v), 1);
}
#[test]
fn multiple_elements() {
let v = vec![1, 2, 3, 4, 5];
assert_eq!(sum(&v), 15);
}
#[test]
fn array_slice() {
let v = [1, 2, 3, 4, 5];
assert_eq!(sum(&v), 15);
}
}
解説
早速答えです、スライスが持つメソッド iter
を呼び出しているだけでシンプルです!
fn sum(array: &[u32]) -> u32 {
array.iter().sum()
}
String
についてその参照として特化した &str
があったように、Vec
や配列に対して特化した型であるスライス &[T]
が登場しました。
関数にてスライスとして型を宣言することで、「スタック上の静的配列 [T; N]
の参照」、「ヒープ上の動的配列 Vec<T>
の参照」両方を受け取れるようになります!
fn main() {
println!("{}", sum(vec![0, 1, 2, 3].as_slice()));
println!("{}", sum( [4, 5, 6, 7].as_slice()));
}
String
と &'static str
(文字列リテラル) の両方を受け取れる &str
と同様、配列や動的配列を不変参照として受け取る際はスライス &[T]
として受け取ると良さそうです。
では次の問題に行きましょう!
次の記事: 【24】 可変スライス・下書き構造体 ~構造体で状態表現~
-
自分がそうだったことからの憶測ですが、おそらく多くのRustaceanが初めてターボフィッシュを知るのはコンパイルエラー経由なんじゃないかと思います。 ↩