前回のあらすじ(#2:2026/1/18)
-
カニ本が届いた!ほんのりワクワク(昔の100%が今は35%)
-
分厚さを見て、Programming Windows 亡霊が蘇った
-
帯の「足を撃つ」メッセージにちょっと身に覚えがあった
-
まえがきを読み、「思想が強い」本だと理解した
-
1章「システムプログラマにもっといいものを」
- Rust はプログラマの負担を引き受ける
- 並列プログラムを飼いならす
- にもかかわらず速い(ゼロコスト抽象化)
- 協調しやすい(Cargo / rustfmt / clippy)
思想から実装へ
前回、私は Rust の「思想」をコンパイルした。
安全と速度の両立。コンパイラが引き受ける責任。並列プログラミングに対する強力なサポート。どれも崇高で、美しく、そして抽象的だ。
1章を読み終えた時点での私は、言うなれば 戦場に向かう兵士が、出征前夜に将軍の演説を聞いた状態 だ。士気は上がった。大義は理解した。しかし、まだ一発も撃っていない。
2章「Rustツアー」。
ここからは、実弾射撃である。
ジムの誠実な前置き
2章の冒頭で、著者の Jim はこう切り出す。
Rust の特徴は、最初のページで見せびらかすことができるような派手な機能ではない
……正直だな、ジム。
普通、プログラミング言語の入門書は、序盤で読者の心を掴もうとする。「こんなに簡単に書けます!」「たった3行でWebサーバが立ちます!」みたいな、キャッチーなデモを見せて、ドーパミンを出させる。
Python なんかは、そういうのが抜群にうまい。import antigravity と書くだけで漫画が開くような、遊び心がある。
でも Jim は、そういうことをしない。
「Rust の良さは、すぐには見えない。でも、ちゃんと説明する」
この誠実さは、好感が持てる。
わかるが、商売としては大変だよな とも思う。
これからプログラミングを学ぶ若者に、
「この言語はね、最初の数ページでは良さが伝わらないんだけどね」
って言うの、だいぶ厳しい。
一方、私は違う。私はもう人生の序盤ではない。
私のような、すでに何度か戦場で泥水を啜った人間に向けて、「次の武器はこれだ」と差し出している。そういう本だ。
風呂敷を広げるジム
続いて Jim はこう言う。
個々の言語機能を1つずつ説明する前に、小さいが完結したプログラムをいくつか示す
良いぞ。ぜひそうしたまえ。
私はどのような言語を学ぶ時でも、序盤で 「コマンドライン処理」「ファイル操作」「文字列操作」 に触れるべきだと思っている。言語の入門書を読み終えて、コマンドラインすら扱えないのでは話にならない。
この章ではそれに加えて、HTTP通信、マルチスレッド、ユニットテストも書くと言っている。
……ジム。
ちょっと風呂敷を広げすぎじゃないのかい?
1つの章で、コマンドライン処理、ファイル操作、HTTP サーバ、並列プログラミング、ユニットテスト。
居酒屋で「とりあえずビール」のつもりで入ったら、「本日のコースは前菜からデザートまで全7品です」と言われた気分だ。
まあいい。Jim を信じよう。ここまでの誠実さは、信頼に値する。
【技術解説:Rustツアーの狙い(たぶん)】
このカニ本は、単機能の説明を積み重ねる前に、
- 小さいが実用っぽいプログラムを先に見せる
- そこで出てくる「知らない要素」は後で回収する(※回収時期は遅いことがある)
という作りになっている。
学習者としては「え、今これ説明しないの?」と不安になるが、
逆に言うと “全体像を見せてから詳細に降りる” 方式なので、ツアーの時点では「いつか何かに使うそのような機能がある」とだけ認識しておけば良い(はず)。
「rustupとCargo」——再会の復習
この節の内容の半分くらいは、実はカニ本が届く前に自力で済ませている(#1 〜Vimの矜持、VSCodeに膝を折る〜)。
rustup でツールチェーンを入れて、cargo new でプロジェクトを作って、cargo run で動かす。このあたりは既知だ。
だが、改めてカニ本の説明を読むと、当時は見落としていた部分に気づく。
cargo は単なるビルドツールではない。プロジェクトの雛形生成、依存関係の解決、テストの実行、ドキュメントの生成。C++ で言えば CMake と vcpkg と CTest と Doxygen を全部まとめて、しかもちゃんと動くようにしたようなものだ。
……「ちゃんと動く」。この一言がどれだけ重いか、CMake と格闘したことのある人間にはわかるだろう。
C++ の世界では、ビルドシステムの設定だけで半日が溶けることがある。「ライブラリが見つからない」「リンクエラーが出る」「Windows だと通るのに Linux だと通らない」。そういう 本質とは無関係な苦行 に、どれだけの時間を奪われてきたことか。
Cargo は、その苦行を最初から存在させない。
復習のつもりで読んだはずが、Cargo への尊敬の念だけが新たに積み上がった。
【技術解説:rustup と Cargo の役割分担(超ざっくり)】
-
rustup:Rust 本体(ツールチェーン)を管理する人- Rust のバージョン切り替えや追加コンポーネント導入などを面倒見てくれる
-
cargo:プロジェクト運営全部入り-
cargo new(新規作成) -
cargo build(ビルド) -
cargo run(実行) -
cargo test(テスト) -
Cargo.tomlに依存を書けば、だいたい解決に向かう
-
ポイントは 「標準のやり方が一本化されてる」 こと。
属人芸が減る。レビューがしやすい。CI が組みやすい。
つまり、チームの未来がちょっとだけ明るくなる。
「Rustの関数」——Kotlinっぽい顔して、芯が硬い
基本的な関数の書き方。
fn gcd(mut n: u64, mut m: u64) -> u64 {
assert!(n != 0 && m != 0);
while m != 0 {
if m < n {
let t = m;
m = n;
n = t;
}
m = m % n;
}
n
}
ユークリッドの互除法。最大公約数を求めるアルゴリズムだ。
関数の定義を見て、まず思ったのは「Kotlin に似ているな」ということだった。
fn で始まる関数定義、引数名: 型 の順序、-> 戻り値型 の記法。Kotlin を書いたことがある人間なら、この文法に大きな違和感は覚えないだろう。
「なぜ型が後ろなのか」。C/C++ や Java から来た人間は、int n ではなく n: u64 という順序に面食らうかもしれない。しかし、Kotlin、Swift、TypeScript、Go など、近年の言語の多くがこの「名前が先、型が後」の記法を採用している。
理由はいくつかあるが、一番大きいのは 型推論との相性 だ。型が省略可能な場合、「名前: 型」なら型の部分だけを消せばいい。「型 名前」の順だと、型を省略したときに構文が曖昧になる。
私個人としては、この記法には慣れている。というより、Kotlin でさんざん書いてきたので、「なぜそうなのか」に対して、なんとなくの納得感がある。
ここまでは、順調だ。
assert と「パニック」
一つ気になったのは、assert! の扱いだ。
C/C++ の assert() は、リリースビルドでは無効化される。NDEBUG マクロが定義されていると、assert は何もしないマクロに展開される。開発中にだけ動くチェックという位置づけだ。
Rust の assert! は違う。リリースビルドでも省略されない。
その代わり、デバッグビルドでのみ有効な debug_assert! が別で用意されている。
【技術解説:assert! と debug_assert! の違い】
assert! |
debug_assert! |
|
|---|---|---|
| デバッグビルド | 有効 | 有効 |
| リリースビルド | 有効 | 無効 |
| 用途 | 絶対に破られてはならない不変条件 | パフォーマンスに影響するが開発中は欲しいチェック |
C/C++ では assert がリリースで消えるため、本番環境で「assert で守っていたはずの前提条件」が黙って破られ、原因不明のバグに発展するケースがある。
Rust は「本当に守りたい条件は、本番でも守れ」という思想だ。パフォーマンスが気になるチェックだけ debug_assert! に逃がす。
この設計は、「安全側に倒す」という Rust の一貫した姿勢を反映している。
そして、Rust では assert! の失敗などでプログラムが突然終了することを パニック と呼ぶらしい。
パニック。
いい名前だ。正直で、切迫感がある。
そういえば、C++ ではセグメンテーションフォルトやスタックオーバーフローなど、様々な理由でプログラムが異常終了するが、それらを包括的に何と呼ぶかは、あまり明確に定義されていないような気がする。「クラッシュした」「落ちた」「死んだ」——我々は感覚的な言葉で呼んでいた。
Rust は、それに正式な名前をつけた。パニック。
プログラムが「パニックを起こす」。人間みたいで、なんだか愛嬌がある。
「return は早期離脱にしか使わない」
#1 でも触れたが、改めて本書で強調されているこの思想。
Rust の関数で
returnを使うのは、関数からの早期離脱の場合だけ
……お、おう。
「はい、別にそれで良いです。理解しました。」
私の25年間の指が反射的に return result; と打とうとする衝動を、意識の力で押さえつける。
わかっている。最後の式がセミコロンなしで書かれていれば、それが戻り値になる。#1 で学んだ。理解している。
だが、理解していることと、体が従うことは別だ。
この「return を書かない」という規約には、なんというか、威厳 を感じる。いや、威圧感か。
「余計なものは書くな。お前の意図はコードから読み取る」
コンパイラにそう言われている気がする。
「ユニットテストの記述と実行」——ありがたい内蔵
#[test]
fn test_gcd() {
assert_eq!(gcd(14, 15), 1);
assert_eq!(gcd(2 * 3 * 5 * 11 * 17,
3 * 7 * 11 * 13 * 19),
3 * 11);
}
ユニットテストが言語レベルで組み込まれている。
#[test] アトリビュートをつけた関数は、cargo test で自動的にテストとして実行される。テスト用のフレームワークを別途インストールする必要がない。設定ファイルを書く必要もない。
これは、ありがたい。
C++ の世界でテストを書こうとすると、まず Google Test を入れるか Catch2 を入れるかで悩み、CMake の設定を書き、CTest との連携を考え……と、テストを書く前の準備だけで消耗する。
そして CTest。あれは……だいぶアレだ。詳しくは言わないが、C++ の世界で「テストを書くのが面倒」と言われる理由の半分くらいは、CTest の存在に起因していると、私は密かに思っている。
Rust では cargo test 一発。それだけ。
$ cargo test
Compiling iron-gcd v0.1.0 (/home/abe/pepperoni/iron-gcd)
Finished test [unoptimized + debuginfo] target(s) in 0.35s
Running unittests src/main.rs
running 1 test
test test_gcd ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
良いね。そうでなくちゃ。
テストは「書くのが面倒」だから書かれないのであって、書くのが簡単なら、みんな書く。Rust はその障壁を、言語レベルで取り払っている。
【技術解説:Rust のユニットテスト】
Rust のテストは、特別なフレームワークを必要としない。
基本的な仕組み:
-
#[test]アトリビュートをつけた関数が、テスト対象になる -
assert!、assert_eq!、assert_ne!マクロで検証する -
cargo testで全テストが実行される - テスト関数がパニックすれば失敗、正常終了すれば成功
テストの配置:
テストコードは、テスト対象と同じファイルに書ける。#[cfg(test)] モジュール内に置くのが慣例で、これはテスト実行時にのみコンパイルされる。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcd() {
assert_eq!(gcd(14, 15), 1);
}
}
C++ の Google Test では、テスト用の別ファイルを作り、テストバイナリを別途ビルドする構成が一般的だ。Rust は「テストはコードのすぐそばに置く」という文化で、テストを書くまでの距離が格段に近い。
「コマンドライン引数の処理」——ジム、急に走るな、俺の日本語が通じない
ここからだ。
ここから、景色が一変する。
前節までは、Kotlin の経験や C++ の知識で、なんとなく「ああ、そういうことね」と頷きながら読めていた。ジムと私は同じペースで散歩していた。
しかし、コマンドライン引数の処理に入った瞬間、ジムが突然ダッシュを始めた。
use std::str::FromStr;
use std::env;
fn main() {
let mut numbers = Vec::new();
for arg in env::args().skip(1) {
numbers.push(u64::from_str(&arg)
.expect("error parsing argument"));
}
if numbers.len() == 0 {
eprintln!("Usage: gcd NUMBER ...");
std::process::exit(1);
}
let mut d = numbers[0];
for m in &numbers[1..] {
d = gcd(d, *m);
}
println!("The greatest common divisor of {:?} is {}", numbers, d);
}
……なんだこれは。
わからん。
いや、プログラムの「やりたいこと」はわかる。コマンドライン引数を数値に変換して、最大公約数を求める。25年プログラマーをやっていれば、そのくらいは推測できる。
だが、Rust の文法がわからない。
一つずつ見ていこう。混乱している頭を整理するために。
第一の混乱:トレイトってなんだ
use std::str::FromStr;
Jim は冒頭でこの use 文を書き、そして u64::from_str(&arg) を使う。
ちょっと待ってくれ。
std::str::FromStr を use することで、なんで u64 に from_str メソッドが生えるんだ?
u64 は数値型だろう? FromStr は文字列処理のモジュールにあるんだろう? なんで文字列のモジュールを持ってきたら、数値型にメソッドが追加されるんだ?
Jim は言う。
「後で説明する」
はい……。
「11章でな」
え……。
11章。600ページ超の本の、11章。今、私は2章にいる。
ジム、それは「後で」じゃない。「はるか彼方で」 だ。
とりあえず「トレイト」という概念があって、それを use すると型にメソッドが追加される、ということだけ受け入れて先に進む。
【技術解説:トレイトとは何か(2章時点でのざっくり理解)】
※ 本格的な解説は11章の内容を待つとして、今の時点での理解をメモしておく。
トレイト (trait) は、Rust における「振る舞いの定義」だ。Java のインターフェースや、C++ の純粋仮想関数を持つ抽象クラスに近い概念……らしい。
FromStr トレイトは「文字列から自分自身の型を生成できる」という振る舞いを定義している。u64 はこのトレイトを実装しているため、u64::from_str("42") のように呼び出せる。
ただし、そのメソッドを使うためには、トレイトを use でスコープに持ち込む必要がある。
use std::str::FromStr;
let n = u64::from_str("42"); // OK
// use なし
let n = u64::from_str("42"); // コンパイルエラー
C++ で例えるなら、ヘッダーを #include しないと関数が使えないのと似ている……のか? いや、ちょっと違う気もする。
正直、この仕組みが腑に落ちるのは、もう少し先になりそうだ。
第二の混乱:.skip(1) ?
for arg in env::args().skip(1) {
skip(1) で最初の引数(プログラム名)を飛ばしている。
skip ? step ちゃうんか。
……いや、C++ の std::advance が脳内で手を挙げている。Python の next も挙手している。私の脳内の各言語代表団が「うちでは○○って言うんですけど」と口々に主張し始めて収拾がつかない。
まあ、意味はわかる。「1つ飛ばす」。skip。直球。嫌いじゃない。
でも、イテレータのメソッドチェーンでこう書くのか。C の argv[1] から始めるのとは、だいぶ世界が違う。
これは良し悪しではなく、文化の違い だ。C/C++ は「配列のインデックスを自分で管理する」文化。Rust は「イテレータに任せる」文化。
脳の回路を切り替える必要がある。
第三の混乱:アンパサンドの嵐
u64::from_str(&arg)
&arg
へ? アンパサンドは習ってませんが。
C/C++ 脳が即座に反応する。「変数のアドレスを渡すってことでいいんですか?」
合ってますか? ……合ってない気がする。Rust には所有権があって、参照と借用があって、ポインタとは違うと、さっき1章で読んだばかりだ。
でも見た目は完全にアドレス演算子なんだよなぁ。
そして数行下には、
for m in &numbers[1..] {
また & だ。 しかも今度はスライスについている。
さらに、
d = gcd(d, *m);
*m アスタリスク。C/C++ なら間接参照演算子だ。
え? ちょっと待って。m はポインタだったの? さっきの & は参照を作って、ここの * は参照外し?
意味不明なシンタックスが飛び交う。
私の C++ 経験が、むしろ邪魔をしている。& と * の意味を「知っているつもり」だからこそ、Rust での意味とのズレに混乱する。完全な初心者のほうが、素直に受け入れられるのかもしれない。
【技術解説:Rust の & と *(2章時点の整理)】
C/C++ 経験者が最初に混乱するポイント。
& は「参照を作る」演算子
let s = String::from("hello");
let r = &s; // s への参照を作る
C++ の参照 (const string& r = s;) に似ているが、Rust では「借用」という概念と結びついている。所有権を移動させずに値を利用するための仕組み。
* は「参照を外す」演算子
let r = &42;
let n = *r; // 42
C のポインタの間接参照に見た目は似ている。実のところ内部的にはアドレスを持っているという意味でポインタに近いが、Rust の参照はコンパイラが有効性を保証しており、null になることはない。
スライスの &numbers[1..]
これは「numbers の2番目以降の要素への参照(スライス)」を作っている。配列全体をコピーせず、元のデータの一部分を「借りている」。
2章時点では「& は借りる、* は借りたものを使う」くらいの理解でいいのかもしれない。……たぶん。
第四の混乱:.expect("error") って何
numbers.push(u64::from_str(&arg)
.expect("error parsing argument"));
.expect("error")。
私の Java/Kotlin 脳が反応する。「try-catch は? 例外はどこで捕捉するんだ?」
どうやら、Rust には例外がないらしい。代わりに、Result 型という「成功か失敗かを表す値」を返す。from_str は Result<u64, ParseIntError> を返し、.expect() は成功なら値を取り出し、失敗ならメッセージを表示してパニックする。
つまり、戻り値でエラーを返す というスタイルだ。
C言語の errno チェックに先祖返りしたのか? と一瞬思ったが、そうではない。Result 型は「使わなければコンパイラが警告を出す」ように設計されている(らしい)。C の errno は無視し放題だったが、Rust の Result は無視すると怒られる。
ここでエラーが起きるとパニック。つまりプログラムが即死。例外のような「上位で拾ってリカバリ」の機構は、また別の話のようだ。
第五の混乱::? って何
println!("{:?} of these is {}", numbers, d);
{:?}。
:? について、Jim は特に何も触れていないように見える。私が読み飛ばしたのか? それとも「わかるだろ?」ということなのか?
わからんぞ、ジム。
推測するに、{} は通常の表示、{:?} はデバッグ用の表示、だろうか。ベクタ(配列的なもの)は通常の {} では表示できないから、デバッグ表示の :? を使う、みたいな?
……と思って、試しに {:?} を {} に変えてみた。
error[E0277]: `Vec<u64>` doesn't implement `std::fmt::Display`
やっぱり。Vec は Display(通常表示)を実装していないから {} では表示できない。Debug(デバッグ表示)なら実装されているから {:?} なら表示できる。
……合ってるかどうかすら自信がないが、コンパイラのエラーメッセージを読む限り、そういうことだと思う。
Rust のコンパイラは、エラーメッセージが丁寧だ。「何がダメなのか」だけでなく、「どうすればいいのか」まで教えてくれる。これは #1 で println! の ! を消したときにも感じたことだ。
おばさん(VSCode)に続いて、コンパイラもまた、口うるさいが親切な存在だった。
48歳にして、親切な女性に囲まれて生きている。現実世界ではこうはいかない。
【技術解説:{} と {:?} の違い】
println! マクロで使うフォーマット指定子の違い。
| 指定子 | トレイト | 用途 |
|---|---|---|
{} |
Display |
ユーザー向けの見やすい表示 |
{:?} |
Debug |
デバッグ向けの詳細な表示 |
Display は型に「人が読むためのきれいな表示方法」を定義する。Debug は「プログラマが中身を確認するための表示方法」を定義する。
基本的なプリミティブ型(i32, u64, &str など)は両方実装している。しかし Vec などのコレクション型は Debug のみ実装しており、Display は実装されていない。
let v = vec![1, 2, 3];
println!("{:?}", v); // [1, 2, 3] ← OK
println!("{}", v); // コンパイルエラー
Display を自分で実装すれば {} でも表示できるようになるが、それは先の話だ。
ちなみに {:#?} と書くと、構造体の中身がインデント付きで整形表示される。デバッグ時にはかなり便利。
ジムを呼んできてくれ
正直に告白する。
ここまでの数ページで、私は完全に迷子になった。
トレイト、参照、借用、Result、パニック、スライス、デバッグ表示。知らない概念が次々と現れ、それぞれが「後で説明する」と言い残して消えていく。
前回の #2 で私は「思想のコンパイルは通った。あとは実装だけだ。」と格好をつけた。
実装、まだ始まったばかりなんですが。
しかも「借用」の概念すらまだ理解していないのに、ジムから「後で説明する」の借用証書だけは大量に借用している。これが Rust の言う「借用」なのか? 違うと思う。
しかし、Jim のやり方は間違っていないとも思う。彼は2章の冒頭で宣言している。「個々の言語機能を1つずつ説明する前に、小さいが完結したプログラムをいくつか示す」と。
つまり、ここでは 全部を理解する必要はない。
「Rust のプログラムはこういう雰囲気で書く」というのを、まず体で感じる章なのだ。料理のフルコースを一口ずつ味見して、「ああ、こういうジャンルの店なのね」と掴む。レシピは後で教えてもらう。
わかった、ジム。信じるよ。
でも、もうちょっとゆっくり走ってくれ。
「Webページを公開する」——なんとか食らいつく
コマンドライン引数で膝を折りかけたが、次は Web ...
「え、もう Web やるの?」
さっきまで最大公約数(gcd)で遊んでたのに、次のページで HTTP になる。
この切り替え、昭和の人間には刺激が強い。
ファミコンの次の章がいきなり PlayStation みたいなテンポ。
ただ、ここは「雰囲気」でついていける。
なぜなら現代の我々は、だいたい Web に飼いならされているからだ。
ここでは actix-web クレートを使って、簡単な Web サーバを立てる。
フォームから2つの数値を受け取って、最大公約数を計算して返すだけの、シンプルなアプリケーション。
コードの全体像は、正直まだ完全には読めない。しかし、「何をやっているか」はわかる。HTML フォームを表示して、POST リクエストを処理して、結果を返す。Web アプリケーションの基本中の基本だ。
印象的だったのは、外部クレート(ライブラリ)を使う手順の簡単さだ。
※クレート(crate)は「輸送用の箱」を意味するそうな。ちなみに Cargo は「貨物」
Cargo.toml にクレート名とバージョンを書くだけ。あとは cargo build すれば、勝手にダウンロードされてビルドされる。
[dependencies]
actix-web = "1.0.8"
serde = { version = "1.0", features = ["derive"] }
C++ で外部ライブラリを使おうとした時の、あの苦行を思い出す。ソースをダウンロードして、ビルドして、パスを通して、リンク設定を書いて、プラットフォームごとに場合分けして……。
Cargo は、その全てを2行で解決する。
#2 で読んだ「Rustでは協調するのも容易」の意味が、ここでようやく実感を伴って理解できた。
「並列プログラミング」——ベースキャンプで撤退を決める
ここが完全に魔境だった。
Mandelbrot 集合を並列計算で描画するプログラム。
クロージャ、スレッド、crossbeam、ムーブセマンティクス。聞いたことのある概念と、聞いたこともない概念が、渾然一体となって襲いかかってくる。
1章で「並列プログラムを飼いならす」と読んだ時は、「かっこいいことを言うな」と思った。
飼いならされているのは私の方だった。
ただ、ぱっと見で掴めたことだけ書き残しておく。
まず、Mandelbrot 集合の描画というテーマ選びがうまい。ピクセルごとに独立した計算を行うので、並列化と相性が良い。分割して、それぞれのスレッドに投げて、結果をくっつける。概念としては理解できる。
コード上では、画像のバッファをスレッドごとに分割して渡している……ように見える。そしてそこに crossbeam というクレートが登場し、スコープ付きスレッド(crossbeam::scope)なるものでスレッドを生成している。
ここで Jim が強調しているのは、Rust では「あるスレッドが使っているデータを、別のスレッドが勝手に書き換える」ことをコンパイラが防ぐ ということだ。
C++ で std::thread を使った経験がある身としては、これがどれだけありがたいかは想像がつく。データ競合は、発生しても再現しにくく、デバッグは悪夢だ。それを実行前に弾いてくれるなら、確かに革命的だ。
……と、理屈では わかる。
だが、コードの具体的な動きを追おうとすると、途端に霧が立ち込める。クロージャの move は何を移動しているのか。スレッドに渡されたバッファの所有権はどうなっているのか。crossbeam::scope が通常の std::thread::spawn と何が違うのか。
一つ一つの疑問が、さらに深い疑問を呼ぶ。ここは底なし沼だ。
正直に言おう。このセクション、私はまだちゃんと読めていない。
ここでも Jim は「後の章で詳しく説明する」と言う。
わかった。わかったから。
もう「後で」の借用証書が溜まりすぎて、私の脳内は債務超過だ。
登山に例えるなら、山頂はまだ雲の中だが、ベースキャンプまでは来た。地形の概要は見えた。装備が足りないこともわかった。
ここは、一旦撤退する。
この並列プログラミングのセクションは、後日、別の回でちゃんと登り直すつもりだ。所有権と借用を理解し、クロージャを理解し、スレッドを理解してから、改めてこの Mandelbrot に挑む。
「理解してから書く」のではなく「書いてから理解する」。2章はそういう章なのだと、ようやく悟った。ただし、悟ったことと登頂できることは、やはり別の話だ。
「ファイルシステムとコマンドラインツール」——静かな着地
最後のセクション。コマンドラインから正規表現でファイル内をテキスト検索するツール。いわゆる grep のミニ版だ。
ここに来て、ようやく少し落ち着いた。
ファイルを開いて、一行ずつ読んで、正規表現でマッチさせて、結果を出力する。プログラムの構造としては、25年前から変わっていない。言語が変わっても、「テキストファイルを読んで処理する」という基本形は不変だ。
エラーハンドリングに Result と ? 演算子が使われているが、これも「後で説明する」系だ。もう慣れた。借用証書をもう1枚、山に積むだけだ。
2章を終えて
2章「Rustツアー」。
全体を振り返ると、Jim の意図は明確だった。
「Rust はこういう言語だ。細かいことは後で教える。まずは風景を見ろ」
バスツアーで車窓から街並みを眺めるようなものだ。「あの建物は何ですか?」と聞いても、ガイドは「後日ゆっくりご案内します」と言って、バスは止まらない。
正直、ストレスはある。わからないことだらけで先に進むのは、25年の経験がある人間にとっては特に居心地が悪い。「わかった上で次に進みたい」という職業的な強迫観念がある。
だが、それは 古い学び方 なのかもしれない。
全体像を先に見せて、個別の理解は後から埋めていく。トップダウンのアプローチだ。Jim はそれを、意図的にやっている。
私は、そのやり方に身を委ねてみることにする。
それにしても、2章だけでこれだけの密度だ。コマンドライン処理、ユニットテスト、Webサーバ、並列プログラミング、ファイル操作。一章分の内容で、他の言語の入門書なら5章分はある。
ジムの風呂敷は、確かに大きかった。だが、その風呂敷の中身は、ちゃんと詰まっていた。
【技術解説:2章で登場した新概念の交通整理】
2章はツアーなので、全部を深く理解する必要はない。だが、「何が登場したか」だけは整理しておく。後で立ち戻るための地図として。
| 概念 | ざっくり | 詳細が出る章 |
|---|---|---|
トレイト (FromStr 等) |
型に「できること」を追加する仕組み | 11章 |
参照 (&) |
所有権を移さずに値を借りる | 5章 |
スライス (&numbers[1..]) |
配列の一部分への参照 | 3章 |
| Result 型 | 成功/失敗を表す列挙型 | 7章 |
.expect() |
Result から値を取り出す(失敗時パニック) | 7章 |
? 演算子 |
Result のエラーを呼び出し元に伝播する | 7章 |
| クロージャ | 環境をキャプチャする無名関数 | 14章 |
| スレッド | 並列処理の基本単位 | 19章 |
| クレート | Rust のライブラリパッケージ | 8章 |
こうして並べると、Jim の「後で説明する」は「後で説明する」のではなく、「この本の全体を通じて説明する」 ということだったのだと気づく。
2章は、これからの旅の目次なのだ。
今回の学び
- Cargo の便利さは、使えば使うほど実感する(C++ のビルドシステム地獄を知る者として)
-
assert!はリリースビルドでも有効。debug_assert!と使い分ける - Rust のプログラムが突然死することを「パニック」と呼ぶ
- ユニットテストが言語組み込みで、
cargo test一発で実行できる - トレイト、参照、借用、Result 型……新概念が大量に登場したが、2章では「雰囲気を掴む」ことが目的
-
&は借用、*は参照外し。C/C++ の経験がかえって混乱を招く -
{:?}はDebugトレイトによるデバッグ表示。Vec等のコレクションを表示するのに必要 - クレート(crate)は「外部ライブラリ」※語源は輸送用の箱
- 「理解してから書く」ではなく「書いてから理解する」——そういう学び方もある
……こうして書き出してみると、「学び」の項目数だけはまあまあ立派だ。理解の深さは、また別の話だが。
Abe
「Rust ツアーは観光ではなく、踏破型アスレチック」