『The Rust Programming Language 日本語版』を読む
Rustの美しさに魅了されてしまった。
しかし、規模の大きいコードを書こうとすると理解の出来ないエラーが発生し、解決できずに頓挫してしまう。
そのためRustを根本から理解するため、本腰を入れて基礎から勉強する。
『The Rust Programming Language』と公式の問題集 rust-lang/rustlings は(ある程度)対応しているそうなので、これも解きながら学び進める。
0.1. まえがき
低レベルな制御に「下がる」必要があるプログラマは、お決まりのクラッシュやセキュリティホールのリスクを負わず、 気まぐれなツールチェーンのデリケートな部分を学ぶ必要なくRustで同じことができます。さらにいいことに、 Rustは、スピードとメモリ使用の観点で効率的な信頼性の高いコードへと自然に導くよう設計されています。
例えば、 Rustで並列性を導入することは、比較的低リスクです: コンパイラが伝統的なミスを捕捉してくれるのです。 そして、クラッシュや脆弱性の糸口を誤って導入しないという自信を持ってコードの大胆な最適化に取り組めるのです。
これは今までRustを書いてきて十分に体感した。
ウェブアプリを書いてRustを学び、それからその同じ技術をラズベリーパイを対象に適用できるのです。
ラズパイ好きなので例に上がってるのが嬉しい。
0.1. はじめに
Rustにおいては、 コンパイラが並行性のバグも含めたこのようなとらえどころのないバグのあるコードをコンパイルするのを拒むことで、 門番の役割を担います。
Rustのコンパイラが非常に厳格であることは意図的なものであることを理解した。
Rustは、スピードと安定性を言語に渇望する方向けです。ここでいうスピードとは、 Rustで作れるプログラムのスピードとソースコードを書くスピードのことです。
Rustは(私にとって)難解であるため、書くスピードが上がるということに疑問を感じた。
だが、Rustのシステムは非常に美しい体系である(と感じている)ので、その全体像を理解出来たらコードを書くスピードも上がるのではないか。
この本には2種類の章があるとわかるでしょう: 概念の章とプロジェクトの章です。概念の章では、 Rustの一面を学ぶでしょう。プロジェクトの章では、それまでに学んだことを適用して一緒に小さなプログラムを構築します。 2、12、20章がプロジェクトの章です。つまり、残りは概念の章です。
概念単位の解説 -> 実際のプログラムの解説 という順番で進めていくという構成は非常に分かりやすく、入門用の技術書に多く見られる。
この本を読む間違った方法なんてありません: 飛ばしたければ、どうぞご自由に! 混乱したら、前の章に戻らなければならない可能性もあります。ですが、自分に合った方法でどうぞ。
私はRustの思想も理解したいと考えているので通読しようと思う。
Rustを学ぶ過程で重要な部分は、コンパイラが表示するエラーメッセージを読む方法を学ぶことです: それは動くコードへと導いてくれます。
どんな言語でもエラーの内容を読むことは重要だが、特にRustではコンパイラの出力を読み解くことは、動くコードを作る上で非常に有効であると感じる。
コンパイラの出力形式が分かりやすいだけでなく、メジャーなエラーに対しては修正案なども提示してくれる。
1.1. インストール
Rustは安定性 (stability) を保証しているので、現在この本の例でコンパイルできるものは、新しいバージョンになってもコンパイルでき続けることが保証されます。
言い換えると、どんな新しいバージョンでもこの手順に従ってインストールした安定版なら、 この本の内容で想定通りに動くはずです。
Stable版は破壊的変更がないってこと?
すげぇ
これに加えて、なんらかのリンカが必要になるでしょう。
一般的なRustパッケージの中には、Cコードに依存し、Cコンパイラが必要になるものもあります。
C言語を学んでいた時、コンパイラとリンカを分割している理由が分からなかった。
つまり、リンカに食べさせる中間コードはC言語だけのものではないってことかな。
Rustとrustupをアンインストールするには、シェルから以下のアンインストールスクリプトを実行してください:
$ rustup self uninstall
selfを指定することでrustup自体を操作することが明示的になりよい表現方法だと感じた。
Rustacean (Rustユーザが自分たちのことを呼ぶ、冗談めいたニックネーム)
Rustaceanになりたーい !🦀
訳注1:Rustaceanについて、いらないかもしれない補足です。公式Twitter曰く、Rustaceanはcrustaceans(甲殻類)から来ているそうです。
こういう豆知識的な面白い注釈好き💖
インストールされたRustには、ローカルに複製されたドキュメンテーションのコピーが含まれているので、これをオフラインで閲覧することができます。 ブラウザでローカルのドキュメンテーションを開くには、rustup docを実行してください。
1.2. Hello, World!
ファイル名に2単語以上使っているなら、アンダースコアで区切ってください。例えば、helloworld.rsではなく、 hello_world.rsを使用してください。
命名規則大事。
とりあえず、!を使用すると、普通の関数ではなくマクロを呼んでいるのだということを知っておくだけでいいでしょう。
難しいことはさらっと解説し、詳しくは後から解説するスタイル。
Hello, world!が確かに出力されたら、おめでとうございます!正式にRustプログラムを書きました。 Rustプログラマになったのです!ようこそ!
🎉🎉🎉
つまり、プログラムをコンパイルし、 実行可能ファイルを誰かにあげ、あげた人がRustをインストールしていなくても実行できるわけです。 誰かに .rb、.py、.jsファイルをあげたら、それぞれRuby、Python、JavaScriptの処理系がインストールされている必要があります。 ですが、そのような言語では、プログラムをコンパイルし実行するには、1コマンドしか必要ないのです。 全ては言語設計においてトレードオフなのです。
コンパイルの必要性に関するトレードオフの説明。分かりやすい。
次は、Cargoツールを紹介します。 これは、現実世界のRustプログラムを書く手助けをしてくれるでしょう。
現実世界(英語版でもreal-world
)という表現、少し違和感を感じたが喩えとして分かりやすい。
1.3. Hello, Cargo!
題を 1.2章 Hello, World! に対して Hello, Cargo! にしているの良いね。
CargoはRustのビルドシステム兼パッケージマネージャです。 ほとんどのRustaceanはこのツールを使ってRustプロジェクトを管理しています。 なぜなら、Cargoは多くの仕事、たとえばコードのビルド、コードが依存するライブラリのダウンロード、それらのライブラリのビルドなどを扱ってくれるからです。
このファイルはTOML(Tom's Obvious, Minimal Language、トムの明確な最小限の言語)形式で、Cargoの設定フォーマットです。
TOMLファイルの正式名称初めて知った!
私も大成してTOML, WezTermのように製品に自分の名前を入れたい。
Rustではコードのパッケージのことをクレートと呼びます。
cargo build
を初めて実行したとき、Cargoは最上位にCargo.lockという新しいファイルを作成します。 このファイルはプロジェクト内の依存関係の正確なバージョンを記録しています。
Cargoは
cargo check
というコマンドも提供しています。 このコマンドはコードがコンパイルできるか素早くチェックしますが、実行ファイルは生成しません。
cargo check
を使うとバイナリを生成せずにプロジェクトをビルドして、エラーがないか確認できる。
今まで‘cargo build`を使用していたのでぜひ使わせていただきます。
プロジェクトが最終的にリリースできるようになったら、cargo build --releaseを使い、最適化した状態でコンパイルできます。 このコマンドは実行ファイルを、target/debugではなく、target/releaseに作成します。
コードの実行時間をベンチマークするなら、必ずcargo build --releaseを実行し、target/releaseの実行ファイルを使ってベンチマークを取ってください。
2. 数宛てゲームのプログラミング
ハンズオン形式のプロジェクトに一緒に取り組むことで、Rustの世界に飛び込んでみましょう! この章ではRustの一般的な概念を、実際のプログラムでの使い方を示しながら紹介します。 let、match、メソッド、関連関数、外部クレートの使いかたなどについて学びます! これらについての詳細は後続の章で取り上げますので、この章では基本的なところを練習します。
ツール類のセットアップの後に実際に1つプロジェクトを作成するという構成。全体像が掴めて非常にいいと思う。
Rustはデフォルトで、標準ライブラリで定義されているアイテムの中のいくつかを、すべてのプログラムのスコープに取り込みます。 このセットはprelude(プレリュード)と呼ばれ、標準ライブラリのドキュメントでその中のすべてを見ることができます。
等号記号(=)はRustに、いまこの変数を何かに束縛したいことを伝えます。
等号記号の役割を「束縛」とするのはRustならではだと思う。
Stringは標準ライブラリによって提供される文字列型で、サイズが拡張可能な、UTF-8でエンコードされたテキスト片になります。
Rustの文字コードはまだ理解できていないが、ASCIIではなくUTF-8であるところがモダン。
::newの行にある::構文はnewがString型の関連関数であることを示しています。 関連関数とは、ある型(ここではString)に対して実装される関数のことです。
これを関連関数というのですね。
つまりlet mut guess = String::new();という行は可変変数を作成し、その変数は現時点では新しい空のStringのインスタンスに束縛されているわけです。 ふう!
ふう !
参照は複雑な機能(訳注:一部のプログラム言語では正しく使うのが難しい機能)ですが、Rustの大きな利点の一つは参照を安全かつ簡単に使用できることです。
Rustは参照が意味を持っているのが良いね。
Rustの標準ライブラリにはResultという名前の型がいくつかあります。
!?
汎用のResultと、io::Resultといったサブモジュール用の特殊な型などです。
なるほど。
この行はユーザの入力を現在保持している文字列を表示します。 一組の波括弧の{}はプレースホルダーです。 {}は値を所定の場所に保持する小さなカニのはさみだと考えてください。
🦀✂
Rustの標準ライブラリには、まだ乱数の機能は含まれていません。 ですが、Rustの開発チームがこの機能を持つrandクレートを提供してくれています。
ランタイムを小さくする思想が現れているのかな。
クレートはRustソースコードを集めたものであることを思い出してください。 私たちがここまで作ってきたプロジェクトはバイナリクレートであり、これは実行可能ファイルになります。 randクレートはライブラリクレートです。 他のプログラムで使用するためのコードが含まれており、単独で実行することはできません。
- クレート
- バイナリクレート
- 実行可能ファイルになる
- ライブラリクレート
- 単独で実行不可
- バイナリクレート
[dependecies]はプロジェクトが依存する外部クレートと必要とするバージョンをCargoに伝えます。 今回はrandクレートを0.8.3というセマンティックバージョン指定子で指定します。 Cargoはセマンティックバージョニング(SemVerと呼ばれることもあります)を理解しており、これはバージョンナンバーを記述するための標準です。 0.8.3という数字は実際には^0.8.3の省略記法で、0.8.3以上0.9.0未満の任意のバージョンを意味します。
これをセマンティックバージョニングと呼ぶのか。
外部依存を持つようになると、Cargoはその依存関係が必要とするすべてについて最新のバージョンをレジストリから取得します。 レジストリとはCrates.ioのデータのコピーです。
なぜコピーなのだろうか ?
Cargoはあなたや他の人があなたのコードをビルドするたびに、同じ生成物をリビルドできるようにするしくみを備えています。
.lockファイルのおかげ
Cargoのもう一つの素晴らしい機能は、cargo doc --openコマンドを走らせると、すべての依存クレートが提供するドキュメントをローカルでビルドして、ブラウザで開いてくれることです。
使ったことなかった。英語をすらすら読めるようになりたい。
Orderingもenumの一つでLess、Greater、Equalという列挙子を持っています。 これらは二つの値を比較したときに得られる3種類の結果です。
知らなかったらif文で実装しちゃいそう。
このページを読んだ価値があった。
match式は複数のアーム(腕)で構成されます。 各アームはマッチさせるパターンと、matchに与えられた値がそのアームのパターンにマッチしたときに実行されるコードで構成されます。
用語: アーム
Rustは強い静的型システムを持ちますが、型推論も備えています。
神!!!
Rustのデフォルトはi32型で、型情報をどこかに追加してRustに異なる数値型だと推論させない限りsecret_numberの型はこれになります。
初めて知った✨
アンダースコアの_はすべての値を受け付けます。
match文も神だね👍
3.1 変数と可変性
第2章で触れた通り、変数は標準で不変になります。これは、 Rustが提供する安全性や簡便な並行性の利点を享受する形でコードを書くための選択の1つです。
安全好き。
第2章の数当てゲームのチュートリアル、「予想と秘密の数字を比較する」節で見たように、前に定義した変数と同じ名前の変数を新しく宣言でき、 新しい変数は、前の変数を覆い隠します。Rustaceanはこれを最初の変数は、 2番目の変数に覆い隠されたと言い、この変数を使用した際に、2番目の変数の値が現れるということです。
これの有用性をまだ見いだせていない。
いちばんしっくり来たのはデバッグしやすくするためという説明だったが、まだ必要性を実感できていない。
データ型
スカラー型は、単独の値を表します。Rustには主に4つのスカラー型があります: 整数、浮動小数点数、論理値、最後に文字です。
浮動小数点数使ったことない...
符号付き数値は、 2の補数表現で保持されます(これが何なのか確信を持てないのであれば、ネットで検索することができます。 まあ要するに、この解説は、この本の範疇外というわけです)。
カバー範囲外の内容を明記しているの👍
加えて、isizeとusize型は、プログラムが動作しているコンピュータの種類に依存します: 64ビットアーキテクチャなら、64ビットですし、32ビットアーキテクチャなら、32ビットになります。
アーキテクチャについてまだ完全に理解ができていなく、(i|u)sizeが必要な理由を分かっていない。低レイヤーをやる上で重要そう。
整数リテラル(訳注: リテラルとは、見たままの値ということ)は、表3-2に示すどの形式でも記述することができます。
リテラルの説明難しいよね...
整数型の基準はi32型です: 64ビットシステム上でも、 この型が普通最速になります。
64bitシステムでのi32が最速なんだ... 少し意外
基準型はf64です。 なぜなら、現代のCPUでは、f32とほぼ同スピードにもかかわらず、より精度が高くなるからです。
f32とほぼ同スピードならばf64をデフォルトにするのは合理的。
浮動小数点数は、IEEE-754規格に従って表現されています。f32が単精度浮動小数点数、 f64が倍精度浮動小数点数です。
浮動小数点数の規格については去年高専の授業で習ったが忘れてしまった。OS自作とかしなければ必要ない...? (あと高速逆平方根とか?)
Rustのchar型は、ユニコードのスカラー値を表します。これはつまり、アスキーよりもずっとたくさんのものを表せるということです。 アクセント文字; 中国語、日本語、韓国語文字; 絵文字; ゼロ幅スペースは、全てRustでは、有効なchar型になります。ユニコードスカラー値は、 U+0000からU+D7FFまでとU+E000からU+10FFFFまでの範囲になります。
モダン💖
Rustには、 2種類の基本的な複合型があります: タプルと配列です。
パターンマッチングを通しての分配の他にも、アクセスしたい値の番号をピリオド(.)に続けて書くことで、 タプルの要素に直接アクセスすることもできます。
初めて知った時Rustの他の構文より洗練されていないと感じ意外だった。まぁ、タプルは一時的な値の保持につかって、長期的な値の保持には構造体をつかったほうがいいという考えなのかな。
配列は、ヒープよりもスタック(スタックとヒープについては第4章で詳つまびらかに議論します)にデータのメモリを確保したい時、 または、常に固定長の要素があることを確認したい時に有効です。
スタックに値を保持するメリットは何だろう? 速度?
配列とベクタ型、どちらを使うべきか確信が持てない時は、 おそらくベクタ型を使うべきです。第8章でベクタについて詳細に議論します。
配列の扱いが少しかわいそう。
そう、main関数です。これは、多くのプログラムのエントリーポイント(訳注: プログラム実行時に最初に走る関数のこと)になります。
Rustの関数と変数の命名規則は、スネークケース(訳注: some_variableのような命名規則)を使うのが慣例です。
技術的にはこの実際の値は 実引数と呼ばれますが、普段の会話では、仮引数("parameter")と実引数("argument")を関数定義の変数と関数呼び出し時に渡す実際の値、 両方の意味に区別なく使います(訳注: 日本語では、特別区別する意図がない限り、どちらも単に引数と呼ぶことが多いでしょう)。
parameterとargumentの違いを初めて知った! 仮引数と実引数で区別してるんだね。
関数シグニチャにおいて、各仮引数の型を宣言しなければなりません。これは、Rustの設計において、 意図的な判断です: 関数定義で型注釈が必要不可欠ということは、コンパイラがその意図するところを推し量るのに、 プログラマがコードの他の箇所で使用する必要がないということを意味します。
これに苦しまされてきた... 複数の型を取りたいときはどうすればいいのだろう? Enumに入れるしかない?
Rustは、式指向言語なので、 これは理解しておくべき重要な差異になります。他の言語にこの差異はありませんので、文と式がなんなのかと、 その違いが関数本体にどんな影響を与えるかを見ていきましょう。
"式指向言語"とは?
TODO