前の記事
- 【0】 準備 ← 初回
- ...
- 【2】 if・パニック・演習 ← 前回
- 【3】 可変・ループ・オーバーフロー ← 今回
全記事一覧
- 【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 演習第3回になります!ループ構造2つと、前々回 に関連しているかもしれないオーバーフローの話になります!
今回の関連ページ
では行きましょう!
[02_basic_calculator/06_while] while
問題はこちらです。
// Rewrite the factorial function using a `while` loop.
pub fn factorial(n: u32) -> u32 {
// The `todo!()` macro is a placeholder that the compiler
// interprets as "I'll get back to this later", thus
// suppressing type errors.
// It panics at runtime.
todo!()
}
テストを含めた全体
// Rewrite the factorial function using a `while` loop.
pub fn factorial(n: u32) -> u32 {
// The `todo!()` macro is a placeholder that the compiler
// interprets as "I'll get back to this later", thus
// suppressing type errors.
// It panics at runtime.
todo!()
}
#[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);
}
}
前回、階乗を求めるコードを再帰で書きましたが、これをwhileループで書くという問題のようです。
todo!
マクロはまだ未実装であることを示し、ここに到達するとコードがパニックするようになっています。多分いつかの回で取り扱うのでしょうが、この todo!
マクロは「関数の返り値と同じ型の値を返した」ことにできる特殊な性質があるため、コンパイルエラーにならなかったりします。
ともかく、この部分を置き換えてwhileループにすれば良さそうです。
解説
pub fn factorial(n: u32) -> u32 {
let mut res = 1;
let mut i = 1;
while i <= n {
res *= i;
i += 1;
}
res
}
... めちゃくちゃfor文で書きたい ...while文をこんな風に無理に使うことがないため、逆にテンパってしまいました...
この問題で新しく出てきている要素は2つあります。
- while文
-
mut
キーワード
この節でついでに「可変」を登場させているみたいですね。実際、こういうループ構造で良く使うキーワードだったりするので理にかなっています。
Rustではデフォルトですべての変数は 不変 です。そして意図的に仕組まない限り1、この不変性は構造体から参照、配列まで至るところで保たれています。値が変わらないことはコードを読む人に安心感を与え、「この関数で可変参照渡しされてるんじゃなかろうか...」といった余計な詮索をする必要がない明確なコーディングを実現しています!
しかし全てが不変だと不便な時があり、その代表が繰り返し変数です。そしてこういう可変な変数を用いたいときのために mut
キーワードがあります。このキーワードを書くことで「この変数は副作用を受けて変化することがある」と読み手に常に明示的に伝えることができるのです!
話をwhileループの方に戻しましょう。(if式もそうでしたが)条件節をカッコで包んでいない以外は特に他言語との違いは無いんじゃないかと思います。if式と異なり、評価されたところで返す値が決まらないため式としての性質は持たないです(伏線)。
i++はないの?
インクリメント構文はありません。この辺が理由じゃないかなと思います → Rustでは++すら許されない #C++ - Qiita
そもそも i++
は一つの構文で複数のことをやりすぎです。横着要素はあまり好まない(導入に慎重な)言語ゆえにわざと用意していないと考えられます。
[02_basic_calculator/07_for] for
問題はこちらです。やっと for
で書かせてもらえる...!
// Rewrite the factorial function using a `for` loop.
pub fn factorial(n: u32) -> u32 {
todo!()
}
テストを含めた全体
// Rewrite the factorial function using a `for` loop.
pub fn factorial(n: u32) -> u32 {
todo!()
}
#[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);
}
}
こうして何問もお題として使えるって、階乗は優秀ですね...
解説
pub fn factorial(n: u32) -> u32 {
let mut res = 1;
for i in 1..=n {
res *= i;
}
res
}
繰り返し変数のスコープをforループに閉じ込めることができており、whileループよりもスッキリしたコードになっています!i
に mut
が要らないお陰で、各ループ内では i
が変化しないことが保証されていて非常に良いですね。
ただしまだ可変変数 res
が残っていますね...これすらもイテレータ的な書き方( map
や reduce
を使う等)をすればなくすことができたりします。
後ほど登場するであろうその「イテレータ」に関係していますが、1..=5
は 1, 2, 3, 4, 5
を生成する Range
という型の値で、for文に特有の構文というわけでありません。for文はあくまでも for 変数 in イテレータ {}
という構文であり、 Range
型等のイテレータから要素を一つずつ取り出して処理しています。
loop式
whileやforは値を返さないから式ではなかったのですが、一方でloop式は値を返すことが可能です(伏線回収)。
loop式は loop {}
と書くのですが、これは while true {}
、すなわち無限ループを意味する専用構文です。
無限ループ専用ゆえに抜け出すにはbreak
が必須なため、break
を使用することで値を返すことができる仕様となっています。
let mut i = 0;
let res = loop {
if i == 10 {
break i;
}
println!("{}", i);
i += 1;
};
println!("{}", res);
break i;
で、抜け出した時に式としてi
の中身を返しています!
...まぁ使用機会は少ないと思います。おもしろ構文だったので紹介してみました
[02_basic_calculator/08_overflow] オーバーフロー
問題はこちらです。
// Customize the `dev` profile to wrap around on overflow.
// Check Cargo's documentation to find out the right syntax:
// https://doc.rust-lang.org/cargo/reference/profiles.html
//
// For reasons that we'll explain later, the customization needs to be done in the `Cargo.toml`
// at the root of the repository, not in the `Cargo.toml` of the exercise.
pub fn factorial(n: u32) -> u32 {
let mut result = 1;
for i in 1..=n {
result *= i;
}
result
}
#[cfg(test)]
mod tests {
use crate::factorial;
#[test]
fn twentieth() {
// 20! is 2432902008176640000, which is too large to fit in a u32
// With the default dev profile, this will panic when you run `cargo test`
// We want it to wrap around instead
assert_eq!(factorial(20), 2_192_834_560);
// ☝️
// A large number literal using underscores to improve readability!
}
// ...省略...
}
省略なし
// Customize the `dev` profile to wrap around on overflow.
// Check Cargo's documentation to find out the right syntax:
// https://doc.rust-lang.org/cargo/reference/profiles.html
//
// For reasons that we'll explain later, the customization needs to be done in the `Cargo.toml`
// at the root of the repository, not in the `Cargo.toml` of the exercise.
pub fn factorial(n: u32) -> u32 {
let mut result = 1;
for i in 1..=n {
result *= i;
}
result
}
#[cfg(test)]
mod tests {
use crate::factorial;
#[test]
fn twentieth() {
// 20! is 2432902008176640000, which is too large to fit in a u32
// With the default dev profile, this will panic when you run `cargo test`
// We want it to wrap around instead
assert_eq!(factorial(20), 2_192_834_560);
// ☝️
// A large number literal using underscores to improve readability!
}
#[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);
}
}
ってさっきの問題の答えが書いてる...!?
そして何をすればよいかわかりにくい問題ですが、どうやら「 20! = 2432902008176640000
は大きすぎるからオーバーフローによりパニックしちゃうので、パニックしないように Cargo.toml
の設定を書き換えて! 」という問題らしいです。
解説
プロジェクトのCargo.toml (exercises内の問題ごとのCargo.tomlではなく、大本のCargo.tomlです) の方に、オーバーフロー発生時に無視する設定を追記します。
[workspace]
members = ["exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/ticket_fields"]
resolver = "2"
+ [profile.dev]
+ overflow-checks = false
...いや、無理やりすぎるだろ...?!ちなみに dev
(デバッグビルド) だと overflow-checks
のデフォルト値は true
な一方で、 release
(リリースビルド) だとデフォルト値は false
になっています。AtCoderでRustを選択して解答するとリリースビルドが行われるため、手元(デバッグビルド)だとオーバーフローのせいでRE (ランタイムエラー) になるケースが、回答送信後にWA (誤答) で表示されるという初見殺しがあるのですが、つまりプロファイルのデフォルト値が原因なわけです。
オーバーフローへの対策には色々ありますね。AtCoderで回答する際は 10e9+7
等の素数で剰余を取ったりしますし、筆者が専攻していた暗号学では「有限体」というものを用いて全ての演算結果が必ず有限集合(体)の中に収まるような計算しか取り扱わなかったりします2。
素数での剰余を取るわけではないので演算の整合性が取れなくなる気がしますが、本問のテストケースは単に溢れたらまた0からカウントしている(wrap aroundしている)実装になってそうです。Cargo.tomlを編集してオーバーフローが検知できなくなるのは避けたいので、wrap aroundしていることを踏まえて別解も考えてみました。
pub fn factorial(n: u32) -> u32 {
let mut result = 1u32;
for i in 1..=n {
result = result.wrapping_mul(i);
}
result
}
u32::wrapping_mul
を使えばwrap aroundに演算を行ってくれ、テストを通すことができます!ソースコード側でオーバーフローに配慮されていることを明記していたほうが、一々Cargo.tomlを見なくて済んで良さそうですね。
では次の問題に行きましょう!
次の記事: 【4】 キャスト・構造体 (たまにUFCS)
登場したPlayground
(実際に無効化したことはないですが、)Rust Playground上のデータが喪失する可能性を鑑みて、一応記事にもソースコードを掲載することとしました。
#[derive(Debug)]
struct Hoge<'a>(&'a mut i32);
fn main() {
let mut n = 10_i32;
let hoge = Hoge(&mut n);
println!("{:?}", hoge);
*hoge.0 = 5;
println!("{:?}", hoge);
}
-
例えば「可変参照」を内側に持つ構造体を不変として束縛するといったことをすれば例外的に可変要素を持つ不変な値を作ることはできます。できますがまず自分で書くことはないです。 ↩
-
もちろん、有限体ではなく普通に実数を使う暗号なんかもあったりしますが、筆者は有限体を用いる暗号が好きでした。 ↩