前の記事
- 【0】 準備 ← 初回
- ...
- 【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ~Empowerment 💪 ~ ← 前回
- 【10】 トレイト境界・文字列・Derefトレイト ~ トレイトのアレコレ ~ ← 今回
全記事一覧
- 【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 演習第10回になります!
今回の関連ページ
[04_traits/05_trait_bounds] トレイト境界
問題はこちらです。
// TODO: Add the necessary trait bounds to `min` so that it compiles successfully.
// Refer to the documentation of the `std::cmp` module for more information on the traits you might need.
//
// Note: there are different trait bounds that'll make the compiler happy, but they come with
// different _semantics_. We'll cover those differences later in the course when we talk about ordered
// collections (e.g. BTreeMap).
/// Return the minimum of two values.
pub fn min<T>(left: T, right: T) -> T {
if left <= right {
left
} else {
right
}
}
コンパイルが通るようにしてほしい問題ですね。早速解いちゃいましょう!
解説
今回から ジェネリクス が新登場しています。C++でいうところのテンプレートに似たような機能で、C#とかには普通に存在するものですね。今回は関数について存在していますが、例えば動的配列 Vec<T>
やオプショナル構造体 Option<T>
等の構造体、そしてトレイト等様々なところで目にすることになります。
難しい言い方をするとジェネリクスはある意味で、「型を受け取って型を返す写像」と説明されることがあります。この説明も筆者は好きですが、「型のワイルドカード」とかもう少し直感的な捉え方でも良いかなと思ったり。
話が逸れましたが、今回、このジェネリクス関数のコンパイルを通すには「トレイト境界」を設定する必要があります。T: PartialEq
と書くことによって、 T
に「ただし PartialOrd
を実装している型に限る」という制限を設けています。これがトレイト境界1です。
- pub fn min<T>(left: T, right: T) -> T {
+ pub fn min<T: PartialOrd>(left: T, right: T) -> T {
if left <= right {
left
} else {
right
}
}
T
に PartialOrd
トレイトが実装されていることが確約されているので、内部で PartialOrd
が実装された型でしか使えない機能( <=
)を使用できるようになっています。
トレイト境界はジェネリクス以外のシーンでも、例えばトレイトの定義とかでも登場します(その場合、super trait/sub traitという関係で呼ばれるらしいです)。「トレイトで self
変数が使えるのは奇妙に見えるかも...」みたいなことを前回か前々回か言った気がするのですが、トレイト境界はそのモヤモヤを取っ払ってくれる考えかもしれません。ワイルドカードにしたい型に対して、何でもかんでも許すのではなく、ある程度型に求められる条件が絞られるので、トレイト境界が設けられている場合はどんな型が入ってくるかある程度推測できたりします。
[04_traits/06_str_slice] String
と &str
問題はこちらです。
// TODO: Re-implement `Ticket`'s accessor methods. This time return a `&str` rather than a `&String`.
pub struct Ticket {
title: String,
description: String,
status: String,
}
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
// 省略
}
pub fn title(&self) -> &String {
&self.title
}
pub fn description(&self) -> &String {
&self.description
}
pub fn status(&self) -> &String {
&self.status
}
}
#[cfg(test)]
mod tests {
use super::*;
use common::{valid_description, valid_title};
use std::any::{Any, TypeId};
#[test]
fn test_type() {
let ticket = Ticket::new(valid_title(), valid_description(), "To-Do".to_string());
// Some dark magic to verify that you used the expected return types
assert_eq!(TypeId::of::<str>(), ticket.title().type_id());
assert_eq!(TypeId::of::<str>(), ticket.description().type_id());
assert_eq!(TypeId::of::<str>(), ticket.status().type_id());
}
}
全体
// TODO: Re-implement `Ticket`'s accessor methods. This time return a `&str` rather than a `&String`.
pub struct Ticket {
title: String,
description: String,
status: String,
}
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
if title.is_empty() {
panic!("Title cannot be empty");
}
if title.len() > 50 {
panic!("Title cannot be longer than 50 bytes");
}
if description.is_empty() {
panic!("Description cannot be empty");
}
if description.len() > 500 {
panic!("Description cannot be longer than 500 bytes");
}
if status != "To-Do" && status != "In Progress" && status != "Done" {
panic!("Only `To-Do`, `In Progress`, and `Done` statuses are allowed");
}
Ticket {
title,
description,
status,
}
}
pub fn title(&self) -> &String {
&self.title
}
pub fn description(&self) -> &String {
&self.description
}
pub fn status(&self) -> &String {
&self.status
}
}
#[cfg(test)]
mod tests {
use super::*;
use common::{valid_description, valid_title};
use std::any::{Any, TypeId};
#[test]
fn test_type() {
let ticket = Ticket::new(valid_title(), valid_description(), "To-Do".to_string());
// Some dark magic to verify that you used the expected return types
assert_eq!(TypeId::of::<str>(), ticket.title().type_id());
assert_eq!(TypeId::of::<str>(), ticket.description().type_id());
assert_eq!(TypeId::of::<str>(), ticket.status().type_id());
}
}
このままだとテストで落ちてしまいます。指示通り String
を str
に直してみましょう。
解説
impl Ticket {
pub fn new(title: String, description: String, status: String) -> Ticket {
// 省略
}
- pub fn title(&self) -> &String {
+ pub fn title(&self) -> &str {
&self.title
}
// 他も同様
pub fn description(&self) -> &str {
&self.description
}
pub fn status(&self) -> &str {
&self.status
}
}
テストでは std::any::TypeId
関数を使って str
のタイプIDを取得し、そのタイプIDと ticket.title()
などのゲッターが返す型がちゃんと str
( &str
)になっているか確認しているみたいです。
今回から登場した、そしてRustにおいて至るところで見る &str
は文字列の欠片、文字列スライスと呼ばれるもので、大体はイミュータブル(不変)で文字列の一部を参照するのに使います。深く考える必要はなくて、 &String
を使うシーンがあれば、 &str
で置き換えた方が何かと便利と思っておく程度で良いと思います。(個別に考えなければならないシチュエーションを全て洗うのは大変...)
ところで &String
は &str
とは異なる型のはずなのに、なぜコンパイルが通るのでしょうか?というのは、次のエクササイズでBookにて解説されています。
[04_traits/07_deref] Derefトレイト
「String
の参照 &String
が &str
として振る舞えるのは、 String
に Deref<Target = str>
が実装されているからでした!」というのを踏まえた問題になります。トレイトはなんでも可能にしてしまいますね...
// TODO: whenever `title` and `description` are returned via their accessor methods, they
// should be normalized—i.e. leading and trailing whitespace should be removed.
// There is a method in Rust's standard library that can help with this, but you won't
// find it in the documentation for `String`.
// Can you figure out where it is defined and how to use it?
pub struct Ticket {
title: String,
description: String,
status: String,
}
impl Ticket {
pub fn title(&self) -> &str {
todo!()
}
pub fn description(&self) -> &str {
todo!()
}
}
テストを含めた全体
// TODO: whenever `title` and `description` are returned via their accessor methods, they
// should be normalized—i.e. leading and trailing whitespace should be removed.
// There is a method in Rust's standard library that can help with this, but you won't
// find it in the documentation for `String`.
// Can you figure out where it is defined and how to use it?
pub struct Ticket {
title: String,
description: String,
status: String,
}
impl Ticket {
pub fn title(&self) -> &str {
todo!()
}
pub fn description(&self) -> &str {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalization() {
let ticket = Ticket {
title: " A title ".to_string(),
description: " A description ".to_string(),
status: "To-Do".to_string(),
};
assert_eq!("A title", ticket.title());
assert_eq!("A description", ticket.description());
}
}
解説
要は「 String
型にはなくて &str
型にはあるメソッドをドキュメントから探そう!」という問題です。 trim
というメソッドを使うと文字列前後の空白文字(改行とかも含む)を除いてくれるので、これを使います。
impl Ticket {
pub fn title(&self) -> &str {
self.title.trim()
}
pub fn description(&self) -> &str {
self.description.trim()
}
}
ちなみに「〇〇にはなくて〇〇の Deref
トレイト先なら持ってる」で多分この先n回お世話になるのは Vec<T>
にとっての &[T]
(スライス) じゃないかなと思います。 Vec<T>
にしかないメソッド、 &[T]
側にあるメソッド、両方存在するので、メソッド名だけ聞いてもドキュメントをみないとどちらのメソッドかすぐにはわからないものがしばしばあるかなと思います。(さらにちなみに配列を扱うのは第19回とだいぶ先だったりします)
では次の問題に行きましょう!
次の記事: 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~