「実践 Rustプログラミング入門」を読んだ
全体の感想
他の言語を普段使っていてRustに触れたことがない人にとっては、良い本だと思った。
3部からなり、第1部では基礎的な文法から、Rust特有の考え方(GCの所有権、メモリ安全性、スレッド安全性など)の説明があった。第2部では、実践的なアプリケーションを実装して、コマンドラインツールやWebアプリケーション、GUI、組み込みなど幅広く扱われていた。第3部では、応用的なtipsが紹介されていて、他の言語との連携や、コミュニティ、リリースサイクルなどについて言及されていた。
Rust言語自体は、パフォーマンスがよく、型推論も強くサポートされ、モダンな言語にある機能はほとんど網羅されており、コミュニティも活発で幅広いアプリケーションやツールも作れそうで、いろんな企業が導入し始めてるのも納得できた。特にスレッド安全性などについては、言語の通りに実装すれば未然にバグも防げそうに感じたので、強力だなと思った。
一部パフォーマンスが求められる部分だけRustで書くことも結構簡単にできそうなので、部分的に導入するのも良さそうだと思った。
目次
- 全体の感想
- 目次
- 概要
- 次のアクション
概要
Part 1 入門
Chapter1 プログラミング言語 Rust
1-2 とにかく実行速度が速い
- Rust は機械語に直接コンパイルされる
- ガベージコレクションをもたない
- 「所有権」「借用」「ライフタイム」という新しい仕組みで、言語側で管理する
- 「ゼロコスト抽象化」を追求している
- 抽象化するときにオーバーヘッドとかが生じないようになっている。
1-4 OS から Web アプリケーションまで幅広く実装できる
Chapter3 Rustの基本
3-1 基本的な文法
- NaNは
==
を満たさないので、f32ではPartialEq
やPartialOrd
を使う
3-2 Rustを支える言語機能
所有権と借用
- メモリ管理のために、所有できるのは1つのオブジェクトだけ
- 参照は複数から行えるので、参照渡しをする
- 可変の場合は、一度に一つだけ
- 不変の場合は、制限なし
- デストラクタとして
Drop
トレイトが用意されている
スレッド安全性
マルチスレッドの例
use std::thread; fn main() { let mut handlers = Vec::new(); for x in 0..10 { // moveキーワードで所有権をスレッドに移す handlers.push(thread::spawn(move || { println!("Hello, world!: {}", x); })); } for handle in handles { // スレッドの処理が終了するのを待つ let _ = handle.join(); } }
共有メモリ
use std::rc::{Arc, Mutex}; use std::thread; fn main() { let mut handlers = Vec::new(); let data = Arc::new(Mutex::new(vec![1; 10])); for x in 0..10 { // リファレンスカウンターを増やす let data_ref = data.clone(); handlers.push(thread::spawn(move || { // 可変参照を得る let mut data = data_ref.lock().unwrap(); data_ref[x] += 1; })); } for handle in handles { // スレッドの処理が終了するのを待つ let _ = handle.join(); } dbg!(data); }
メッセージパッシング
use std::sync::mpsc; use std::thread; fn main() { let mut handles = Vec::new(); let mut data = vec![1; 10]; let mut snd_channels = Vec::new(); let mut rcv_channels = Vec::new(); for _ in 0..10 { let (snd_tx, snd_rx) = mpsc::channel(); let (rcv_tx, rcv_rx) = mpsc::channel(); snd_channels.push(snd_tx); rcv_channels.push(rcv_tx); handlers.push(thread::spawn(move || { let mut data = snd_rx.recv().unwrap(); data += 1; let _ = rcv_tx.send(data); })); } for x in 0..10 { let _ = snd_channels[x].send(data[x]); } for x in 0..10 { data[x] = rcv_channels[x].recv().unwrap(); } for handle in handlers { let _ = handle.join(); } dbg!(data) }
Part 2 実践
ソースコードはこちら。
Chapter4 プログラムを作成する
Chapter5 Webアプリケーションの開発
Chapter6 WebAssembly
6-4 サンプルプログラム:ナンバープレースを解く
数独を解く。
JSと比較したベンチマークの結果では、JITコンパイラの最適化の影響のためか、必ずしもRustの方が速いとは限らないみたいだった。
Chapter7 GUIアプリケーション
IcedというGUIのフレームワークのようなライブラリを使って、タイマーのアプリケーションを作成した。
7-1 RustにおけるGUIの現状
GUIクレートの紹介
Chapter8 組み込みシステム
エミュレータを使って、LEDをチカチカさせるものの実装の仕方が紹介されていた。
Chapter9 開発ツール
9-2 フォーマッタ・リンター
9-3 コードカバレッジ
- cargo-tarpaulin: テストのコードカバレッジを測定できる。実行されてない行数も特定できる。
9-4 ベンチマーク・プロファイラ
- ベンチマーク
- プロファイラ
- cargo-profiler
- flamegraph: 可視化してくれる
Chapter10 プロダクトをリリースする
10-2 ビルドの再現性
- Cargo.lock: 依存クレートのバージョン
- rust-toolchain: コンパイラのバージョン
10-3 バイナリサイズの最適化
最適化のオプション
[prifile.release] // Link Time Optimization lto = true // 0: 最適化なし // 1: 基本的な最適化 // 2: 追加の最適化 // 3: すべての最適化(リリースビルドでのデフォルト) // "s": バイナリサイズの最適化 // "z": バイナリサイズの最適化(ループのベクトル化を行わない) opt-level = "z" // コンパイル時の並列度を下げることで、タスクをまたいだ最適化を行える codegen-units = 1 // panic時、スタックを巻き戻してバックトレースを生成する必要がないとき panic = "abort" // シンボル情報の削除 $ strip ./target/release/hello
10-6 ファジング
cargo-fuzzを使える。
Part 3 Tips
Chapter11 いろいろなRustの発展的Tips
11-2 FFIによる他言語との連携
FFI(Foreign Function Interface)によって、他言語と簡単に連携できる。
11-4 unsafe
Rustはメモリ安全性を保証しているが、実装者の責任によってその安全性を無視して実装することができる。
- 極力使わないこと
- 使う場合は、局所的に使うこと
UB (未定義動作)
アプリケーションがライブラリやコンパイラの規約を守らなかったために、どのような動作結果も保証されない状態。
参考資料
11-5 Rust のエディション
Rustでは通常のリリースは6週間ごとに行われている。
大きな変更に関しては、約3年毎にエディションという単位でリリースされる。エディションは互換性があり、クレートごとに指定できる。
11-6 Rust製のOSS
特にすぐ使える有名なもの
11-7 Rust のコミュニティ
最新情報を得るために
次のアクション
もう少し、バックエンドで本番運用できそうか、試してみる。