前の記事
- 【0】 準備
- 【1】 構文・整数・変数 ← 前回
- 【2】 if・パニック・演習 ← 今回
全記事一覧
- 【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 演習第2回になります!今回はif式について、パニックについてと、ここまでをまとめた演習、という題目になっています!
今回の関連ページ
では行きましょう!
[02_basic_calculator/03_if_else] if式
問題はこちらです。
/// Return `true` if `n` is even, `false` otherwise.
fn is_even(n: u32) -> bool {
todo!()
}
// テスト省略
テスト込みver
/// Return `true` if `n` is even, `false` otherwise.
fn is_even(n: u32) -> bool {
todo!()
}
#[cfg(test)]
mod tests {
use crate::is_even;
#[test]
fn one() {
assert!(!is_even(1));
}
#[test]
fn two() {
assert!(is_even(2));
}
#[test]
fn high() {
assert!(!is_even(231));
}
}
n
が偶数なら true
を、 n
が奇数なら false
をそれぞれ返せというものです。
解説
Rustであることを忘れ、ifを使って愚直に書いてみるとこんな感じでしょうか?
fn is_even(n: u32) -> bool {
let result;
if n % 2 == 0 {
result = true;
} else {
result = false;
}
return result;
}
しかしなんとも色々ツッコミたいソースコードです。良い機会ですのでClippyというリンターツールを使ってみたいと思います。
cargo clippy
Checking if_else v0.1.0 (/path/to/100-exercises-to-learn-rust/exercises/02_basic_calculator/03_if_else)
warning: function `is_even` is never used
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:2:4
|
2 | fn is_even(n: u32) -> bool {
| ^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: unneeded `return` statement
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:11:5
|
11 | return result;
| ^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
= note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
|
11 - return result;
11 + result
|
warning: unneeded late initialization
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:3:5
|
3 | let result;
| ^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
= note: `#[warn(clippy::needless_late_init)]` on by default
help: declare `result` here
|
5 | let result = if n % 2 == 0 {
| ++++++++++++
help: remove the assignments from the branches
|
6 ~ true
7 | } else {
8 ~ false
|
help: add a semicolon after the `if` expression
|
9 | };
| +
warning: this if-then-else expression assigns a bool literal
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:5:5
|
5 | / if n % 2 == 0 {
6 | | result = true;
7 | | } else {
8 | | result = false;
9 | | }
| |_____^ help: you can reduce it to: `result = n % 2 == 0;`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
= note: `#[warn(clippy::needless_bool_assign)]` on by default
warning: `if_else` (lib) generated 4 warnings (run `cargo clippy --fix --lib -p if_else` to apply 2 suggestions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
1つ目の warning: function 'is_even' is never used
は無視してしまって構わないです。
-
unneeded 'return' statement
- Rustは 式指向言語 ですから関数の最後の
return
は不要です
- Rustは 式指向言語 ですから関数の最後の
-
unneeded late initialization
- 同じく、式指向言語のため、 if構文も式 (Exercises内での解説) ですから
result
の初期化を各節で行う必要はありません
- 同じく、式指向言語のため、 if構文も式 (Exercises内での解説) ですから
というわけで(最後のを見なかったことにして)ここまでを踏まえるとこんな感じに直せます!
fn is_even(n: u32) -> bool {
if n % 2 == 0 {
true
} else {
false
}
}
冗長な書き方
fn is_even(n: u32) -> bool {
let result = if n % 2 == 0 {
true
} else {
false
};
result
}
- ifは式なので
result
に返り値を束縛できます (冗長な書き方として折りたたんでいます) - 関数は最後に評価された値を返すので、そもそも
result
への束縛は要らないことに気づきます
まとめると、上記コードにより関数の返り値はif式評価後の値となり、n
の偶奇判定を返せました!
... ...
$ cargo clippy
Checking if_else v0.1.0 (/path/to/100-exercises-to-learn-rust/exercises/02_basic_calculator/03_if_else)
warning: function `is_even` is never used
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:2:4
|
2 | fn is_even(n: u32) -> bool {
| ^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: this if-then-else expression returns a bool literal
--> exercises/02_basic_calculator/03_if_else/src/lib.rs:3:5
|
3 | / if n % 2 == 0 {
4 | | true
5 | | } else {
6 | | false
7 | | }
| |_____^ help: you can reduce it to: `n % 2 == 0`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool
= note: `#[warn(clippy::needless_bool)]` on by default
warning: `if_else` (lib) generated 2 warnings (run `cargo clippy --fix --lib -p if_else` to apply 1 suggestion)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
fn is_even(n: u32) -> bool {
n % 2 == 0
}
そもそもif式要らないじゃん...!if式の問題だと思って先入観に囚われていました...
ちなみに模範解答の方もif式は使ってないです。こういう問題にしたのはわざと?
こういう時Clippyは偉大です...!
[02_basic_calculator/04_panics] パニック
問題はこちらです。長いですが解くのに必要な情報を含むためテスト部分まで載せます。
/// Given the start and end points of a journey, and the time it took to complete the journey,
/// calculate the average speed of the journey.
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
// TODO: Panic with a custom message if `time_elapsed` is 0
(end - start) / time_elapsed
}
#[cfg(test)]
mod tests {
use crate::speed;
#[test]
fn case1() {
assert_eq!(speed(0, 10, 10), 1);
}
#[test]
// 👇 With the `#[should_panic]` annotation we can assert that we expect the code
// under test to panic. We can also check the panic message by using `expected`.
// This is all part of Rust's built-in test framework!
#[should_panic(expected = "The journey took no time at all, that's impossible!")]
fn by_zero() {
speed(0, 10, 0);
}
}
まず前提として、 speed
関数は0割り除算が生じるとパニックになります。それ自体は良いのですがその時はテスト側で指定している The journey took no time at all, that's impossible!
というカスタムメッセージを出してほしいようです。
Rust Playground
Exercisesの方で紹介されていたのでこちらでも紹介しておきます。Wandboxやideone、TS playgroundみたいなオンラインエディタのRust特化版です!
Wandbox同様パーマリンクでソースコードを共有できるので、プロジェクトを作成せずに軽く文法を確認したい時や、SNSとかでRustについて語る時に便利だったりします。是非使ってみましょう!
解説
time_elapsed == 0
だったら前もってパニックさせて、そこで表示させたい文言を出力してしまいます。if式と panic
マクロで実現可能です。 (こっちの問題こそとりあえずifがふさわしいのでは...?)
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
// TODO: Panic with a custom message if `time_elapsed` is 0
if time_elapsed == 0 {
panic!("The journey took no time at all, that's impossible!");
}
(end - start) / time_elapsed
}
ちなみにこういうときは assert_*
系のマクロが便利です!time_elapsed != 0
であることを確かめたいので、今回は assert_ne
マクロが使えます。
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
// TODO: Panic with a custom message if `time_elapsed` is 0
assert_ne!(
time_elapsed, 0,
"The journey took no time at all, that's impossible!"
);
(end - start) / time_elapsed
}
assert_*
は「条件を満たしていることを保証している」と見ると読みやすいです。
-
assert_eq
: 第一引数と第二引数が等しいことを保証したい- 等しくないとパニックになる
-
assert_ne
: 第一引数と第二引数が等しくないことを保証したい- 等しいとパニックになる
使い慣れてないと混乱しやすいですね...そして第三引数はパニック時のカスタムメッセージを指定できるので、ここにテストで求められている文言を指定してあげればよいです。
行数的にはifを使う場合とあまり変わらない(どころか1行多い)ですが、「保証」のニュアンスが出るためこっちのほうが読みやすいという人も一定数はいるんじゃないかと思います。
Q: Result型を使うと良いと聞いたのだけど...?
A: はい。こういうエラーになる状況では、いきなりパニックにするのではなくてプログラム側でエラー時の処理をカスタマイズできる Result
型 を使うほうが望ましいです。(100 Exercisesだと中盤で紹介されているみたい...)
慣れないうちはできるだけ Result
型にしておけば確かに間違いないですが、パニック系統も適切な場面で使えるようになるのが理想です。以下、パニックする書き方( もとい unwrap
系メソッド )が有効的な例を示します。
-
コンパイル時に値が確定しており パニックしないことが火を見るより明らかな場合
-
unwrap
を使った方が読みやすくなります
-
- 環境変数や前提となる依存ファイルを
main
関数序盤で読み込むような場合-
Result
による分岐は冗長となることがしばしばです。こういう時はexpect
を使うといい感じになります
-
Result
型はユーザー入力の処理といった「実行時まで入力が確定しない場合」に使うという意識を持っておくと適切に扱えるようになる気がします。大体のプログラムはそういうものなので(偏見)、Result
型が適切という場合が多いのでしょう。
[02_basic_calculator/05_factorial] 関数を書いてみる
問題は...テスト以外コメントしかなかったのでコメントを以下にまとめます。
- 非負整数
n
を受け取る関数factorial
を定義し、n
の階乗(n!
)を返すようにしてください - 階乗は、
n
までのすべての正の整数の積です。例:5! = 5 * 4 * 3 * 2 * 1 = 120
-
0!
は1
と定義されています
-
-
factorial(0)
は1
を返し、factorial(1)
も1
を返し、factorial(2)
は2
を返し...としてください - ここまで習ったことを使ってください。ループはまだ習ってませんね、再帰を使いましょう!
問題コード全体
// Define a function named `factorial` that, given a non-negative integer `n`,
// returns `n!`, the factorial of `n`.
//
// The factorial of `n` is defined as the product of all positive integers up to `n`.
// For example, `5!` (read "five factorial") is `5 * 4 * 3 * 2 * 1`, which is `120`.
// `0!` is defined to be `1`.
//
// We expect `factorial(0)` to return `1`, `factorial(1)` to return `1`,
// `factorial(2)` to return `2`, and so on.
//
// Use only what you learned! No loops yet, so you'll have to use recursion!
#[cfg(test)]
mod tests {
use crate::factorial;
#[test]
fn first() {
assert_eq!(factorial(0), 1);
}
#[test]
fn second() {
assert_eq!(factorial(1), 1);
}
#[test]
fn third() {
assert_eq!(factorial(2), 2);
}
#[test]
fn fifth() {
assert_eq!(factorial(5), 120);
}
}
いや確かにRustではまだループ習ってないけどだからって再帰はいいのかい...まぁいいや
解説
再帰を止めるためのガード節と、再帰による評価部分を書けばいいだけです。
fn factorial(n: u32) -> u32 {
if n == 0 {
return 1;
}
n * factorial(n - 1)
}
今回までの問題のまとめとしては適切でしょう。
-
文法
- 関数を定義するという形で復習になっています
-
非負整数
-
u32
型にしてしまえば負数が引数に渡される心配はなく、特に例外処理を追加する必要はありません
-
-
変数
- 関数の引数という形で登場しています
- if式 と パニック
振り返ってみると良くできた問題でびっくりします。
ループ等本格的な文法はまだ出ていないのに、最後はなかなか興味深い問題でしたね。
では次の問題に行きましょう!
次の記事: 【3】 可変・ループ・オーバーフロー
登場したPlayground
(実際に無効化したことはないですが、)Rust Playground上のデータが喪失する可能性を鑑みて、一応記事にもソースコードを掲載することとしました。
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
let distance = end - start;
distance / time_elapsed
}
fn main() {
speed(1, 10, 0);
}
-
筆者はこういう時ガード節の書き方のほうが好きです。注釈お気持ち表明 ↩