もう一度Rustを勉強したくなったが The Rust Programming Language をやる気力はなかったので、Tour of Rust をやってみた。
大部分が日本語に翻訳されている。ありがたい。
ざっとやってみた感想としては、
- 基本的な文法、概念は一通りカバーできていて良い
- 文字列だけでまるまる1章使ってるのは良い(第6章)
- 所有権、借用、ライフタイムについては難しさに対して説明はさらっとしててこれだけでの完全理解は無理だった
- 第8章 スマートポインタは何もわからない
Tour of Rust をやってもまだ理解が不十分なところとしては
- 所有権と借用
- 構造体にメソッド生やすとき、どういうときに参照を受け取るような設計にして、どういうときに所有権を移動させるべきなのか。参照で済む場合は極力参照?
- ライフタイム
- 具体的なユースケースと一緒じゃないと頭に入ってこない
- 動的ディスパッチ
以下、各章の簡単なメモ。
第1章 - 基礎
- 基本的な型
- 配列型 - コンパイル時に長さが決まる同じ要素のコレクション
- スライス型 - 実行時に長さが決まる同じ要素のコレクション
str
(string slice) - 実行時に長さが決まるテキスト
第2章 - 基本制御フロー
- for文は “イテレータとして評価される式を反復処理します”
- “
..
演算子は、開始番号から終了番号の手前までの数値を生成するイテレータを作成します” - match
matched_num @ 10..=100
で変数に束縛できるの知らなかった
- loop から値を返す
- ブロック式から値を返す
- これで三項演算子的なことができる
let v = if x < 42 { -1 } else { 1 };
第 3 章 - 基本的なデータ構造体
- メモリ:Rustでは次の3種類のメモリ空間を持っている
- 列挙型にはデータを持たせることもできる
第 4 章 - ジェネリック型
- ジェネリック型
- Option
- Result
?
演算子:Result 型が Ok なら値を取り出し、 Err 型ならその場で returnunwrap
やっつけな Option / Result 処理- ベクタ型:
Vec<T>
iter()
を使うとベクタからイテレータを生成できる。for ループに使える
let string_vec = vec![String::from("Hello"), String::from("World")]; for word in string_vec.iter() { println!("{}", word); }
第 5 章 - データの所有権と借用
所有権
型のインスタンスを作成して変数に束縛するとメモリリソースが作成され、そのすべてのライフタイムに渡って Rust コンパイラが検証します。 束縛された変数はリソースの所有者と呼ばれます。
- スコープベースのリソース管理
- スコープが終わるとリソースのデストラクトと解放を行う
• C++ では Resource Acquisition Is Initialization (RAII)「リソース取得は初期化である」とも呼ばれています。****
参照による所有権の可変な借用
struct Foo { x: i32, } fn do_something(f: Foo) { println!("{}", f.x); // f はここでドロップ } fn main() { let mut foo = Foo { x: 42 }; let f = &mut foo; // 失敗: do_something(foo) はここでエラー // foo は可変に借用されており移動できないため // 失敗: foo.x = 13; はここでエラー // foo は可変に借用されている間は変更できないため f.x = 13; // f はここから先では使用されないため、ここでドロップ println!("{}", foo.x); // 可変な借用はドロップされているため変更可能 foo.x = 7; // foo の所有権を関数に移動 do_something(foo); }
- 💬
f
は暗黙的にこれ以上使われてる箇所がないと判断されて途中でドロップするんだな
- 💬
借用したデータの受け渡し
- Rust では、可変な参照が 1 つだけか、不変な参照が複数かのどちらかが許可されます。両方を同時には使用できません。
- 参照は所有者よりも長く存在してはなりません。
- 前者はデータ競合を防ぐ
- 後者は存在しないデータへの参照(ダングリングポインタ)を防ぐ
明示的なライフタイム
関数は、どの引数と戻り値とがライフタイムを共有しているかを、識別のための指定子で明示的に指定できます。
// 引数 foo と戻り値はライフタイムを共有 fn do_something<'a>(foo: &'a Foo) -> &'a i32 { return &foo.x; }
複数のライフタイム
- スタティックライフタイム
'static
という特別なライフタイム指定子- 文字列リテラルは
'static
第6章 - テキスト
文字列リテラルは
&'static str
型str
は常に有効なutf-8であるバイト列を指していることを意味しています。- utf-8 とは
文字列スライス
文字列スライスは、常に有効な utf-8 でなければならないメモリ内のバイト列への参照です。
- 常に有効な utf-8 でなければならないとは? → 絵文字の途中とかでスライスするとパニックする
Chars
- utf-8バイトのシーケンスを
char
型のベクトルとして取得する
let chars = "hi 🦀".chars().collect::<Vec<char>>();
- utf-8バイトのシーケンスを
String
- メモリがヒープ上にあるので拡張や修正が可能
関数パラメータとしてのテキスト
文字列リテラルや文字列は、一般的に文字列スライスとして関数に渡されます。 これによって、実際には所有権を渡す必要がないほとんど場合において柔軟性が増します。****
- 文字列の構築
concat
とjoin
- 文字列のフォーマット
format!
マクロ
- 文字列変換
- 多くの型は
to_string
でStringに変換可能 - 文字列や文字列リテラルから
parse
で型付きの値に変換できる。失敗する可能性があるのでResult
を返す
- 多くの型は
第7章 - オブジェクト指向プログラミング
- オブジェクト指向プログラミングとは?
- Rustはオブジェクト指向プログラミング言語ではない
- データと動作の継承機能を意図的に持っていない
- 選択的な公開による抽象化
pub
キーワードで構造体のフィールドやメソッドをモジュール外に公開- フィールドとメソッドは、デフォルトではそれらが属しているモジュールにのみアクセス可能
- トレイトを用いたポリモーフィズム
trait
はinterfaceみたいなもの
- トレイトに実装されたメソッド
- トレイトは実装されたメソッドを持つこともできる
- 動的ディスパッチと静的ディスパッチ
- トレイトオブジェクト
&dyn MyTrait
型のパラメータに渡すとき、トレイトオブジェクトと呼ばれるものを渡す- 🤔
インスタンスの正しいメソッドを間接的に呼び出すことを可能にするのがトレイトオブジェクトです。 トレイトオブジェクトは、インスタンスのポインタと、インスタンスのメソッドへの関数ポインタのリストを保持する構造体です。
- サイズの合わないデータの処理
- 🤔
トレイトは元の構造体を難読化するため、元のサイズも難読化されます。
- ジェネリック関数
- ジェネリクスに
where
で制限を設けるトレイト境界の話
- ジェネリクスに
- ジェネリクス関数の省略記法
where
のかわりに、引数にimpl Trait
と書くこともできる- 「実践Rust入門」だともう1つ、型パラメータに
<P: Trait>
と書く方法も紹介されていた
- ボックス(Box)
- ジェネリクス構造体
[未翻訳] Chapter 8 - Smart Pointers
- References Revisited
- 参照 (reference) とは、単にメモリ上のバイトの開始位置を示す数字でしかない
- 何が参照とただの数字を異なるものにしているかというと、Rustは参照のライフタイムが参照先(所有者)より長く存在していないことを検証するという点
- Raw Pointers
- 参照は生ポインタ型(raw pointer)というよりプリミティブな型にコンバートできる
- 数値とよく似て、多少の制約はあるもののコピーやムーブができる
- Rustは生ポインタが指し示す先のメモリ位置についてなんの保証もしない
- 2つの生ポインタがある
*const T
不変*mut T
可変
- Dereferencing 参照外し
- The * Operator
- The . Operator
- 勝手に dereference するので
*
つけなくていい
- 勝手に dereference するので
- Smart Pointers
Deref
トレイトで参照外し時の挙動をカスタマイズできる- 同様に
Drop
トレイトでスコープを抜けるときの挙動をカスタマイズできる - このへん
- Smart Unsafe Code
- 数値を生ポインタとみなして参照外しをする場合、Rustはそのメモリ位置が妥当であることを何も保証できないので unsafe ブロックで囲む必要がある
- Familiar Friends
Vec<T>
やString
もスマートポインタ- サンプルコードの
alloc
とかLayout
は理解するの諦めた
- Heap Allocated Memory
- Box 型
- データはヒープに置かれ、そのポインタをスタックに保存する。が、あたかもスタックにデータがあるかのように値に直接アクセスできる
- Failable Main Revisited
fn main() -> Result<(), Box<dyn std::error:Error>>
とするとmainですべてのErrorを捕捉できる****- Errorはトレイトなのでdynにする必要あり
- Referencing Counting
Rc
型。これもスマートポインタの一種- クローンすると参照カウントが増える。カウントが0になるとリソースが解放される
- どういう用途で活きるのかあまりわからない
- 参考:Rc
, the Reference Counted Smart Pointer - The Rust Programming Language
- Sharing Access
RefCell
型- 借用規則を実行時に強制する
- 参考:RefCell
and the Interior Mutability Pattern - The Rust Programming Language
- Sharing Across Threads
Mutex
型- マルチスレッドで使う?
[未翻訳] Chapter 9 - Project Organization and Structure
モジュールの基本的な考え方は昔まとめた:
Rustのモジュールシステムの基本 - dackdive's blog
- Modules
- すべてのRustプログラムあるいはライブラリはクレート
- すべてのクレートはモジュールの階層構造でできている
- すべてのクレートはルートとなるモジュールを持つ
- モジュールは、グローバル変数や関数、構造体、トレイトや他のモジュールを保持できる
- Writing a Program
- プログラムは main.rs というルートモジュールを持つ
- Writing a Library
- ライブラリは lib.rs というルートモジュールを持つ
- Referencing Other Modules and Crates
- モジュール内の各要素を利用する方法。フルパス指定か、
use
キーワード
- モジュール内の各要素を利用する方法。フルパス指定か、
- Referencing Multiple Items
use std::f64::consts::{PI,TAU}
- Creating Modules
foo
というモジュールを定義する方法は2つfoo.rs
foo/mod.rs
- Module Hierarchy
- Inline Module
- Internal Module Referencing
use
で使える特別なキーワード:crate
,super
,self
- Exporting
pub
で他モジュールからも使えるよう公開できる
- Structure Visibility
- 構造体のフィールドも個別に
pub
つける必要がある
- 構造体のフィールドも個別に
- Prelude
- 何も
use
してないのにVec
やBox
が使えるのはなぜか? - →
std::prelude::*
というモジュールは自動的に読み込まれているから
- 何も
- Your Own Prelude
- 独自のライブラリを作ったときも、そのライブラリを使用するための最も一般的なデータ構造を
prelude
として公開すると良い
- 独自のライブラリを作ったときも、そのライブラリを使用するための最も一般的なデータ構造を