Rust_Learning#
私の Rust 学習の記録
Cargo の使用#
cargo new
cargo build
cargo run
cargo check
cargo doc --open #すべての依存関係によって提供されるドキュメントをローカルにビルドし、ブラウザで開きます
参考リンク#
Rust の特徴#
- パターンとマッチ構造
- 強力で静的な型システム
- 型推論
- シャドウ
- Rust は、プログラムがエラーで終了する際に「パニック」という用語を使用します
- 関数定義における型注釈の必要性
- Rust の目標は、プログラムを効率的なバイナリにコンパイルすることで、可能な限り少ないランタイムチェックを必要とします
- Rust の基本的な目標は、プログラムが未定義の動作を持たないことを保証することです
第 3 章 一般的なプログラミング概念#
- 定数と変数
- 定数はデフォルトで不変であるだけでなく、常に不変です。
- 定数は任意のスコープで宣言できます
- 定数は、実行時にのみ計算できる値の結果ではなく、定数式にのみ設定できます。
- シャドウは以下の条件で終了します
- それ自体がシャドウされる
- スコープが終了する
- 整数の除算はゼロに向かって最も近い整数に切り捨てられます
- タプルと配列の違い
- タプルの各型は異なることができます;
- 配列のサイズは固定されており、定義時に指定されます
- タプルはより柔軟で、異なる型を格納でき、一般的に一時的なデータの組み合わせに使用されます
- 配列のサイズは固定されており、大量の同じ型のデータを格納するために使用されます
- 式と文
- 式には終了セミコロンが含まれません
- 文は値を返しません
- 関数の戻り値は関数の本体のブロック内の最終式の値と同義です
- 制御フロー
- これは、if の各アームからの結果となる可能性のある値が同じ型でなければならないことを意味します
- Rust には「真」または「偽」の値の概念がありません。したがって、if 式の条件はブール値でなければなりません
第 4 章 所有権の理解#
- スタックは特定の関数に関連付けられたデータを保持し、ヒープは関数を超えて生存できるデータを保持します
- Rust はプログラムが手動でメモリを解放することを許可しません。このポリシーは、上記のような未定義の動作を回避します。
- Box の解放原則:変数がボックスを所有している場合、Rust が変数のフレームを解放するとき、Rust はボックスのヒープメモリを解放します。
- 移動されたヒープデータの原則:変数 x がヒープデータの所有権を別の変数 y に移動すると、x は移動後に使用できません。
- 参照はポインタの一種です。
- Rust は、ドット演算子でメソッドを呼び出すなど、特定のケースで暗黙的にデリファレンスと参照を挿入します。
- ポインタ安全原則:データは同時にエイリアスされて変更されるべきではありません。
- 権限はパスに対して定義され、変数だけではありません。パスは、代入の左側に置くことができるものです。
- データへの参照を作成する(それを「借りる」)と、そのデータは参照が使用されなくなるまで一時的に読み取り専用になります。
- Rust の借用チェッカーは、a [0]、a [1] などの異なるパスを含みません。a のすべてのインデックスを表す単一のパス a [_] を使用します。
- スライスは特別な種類の参照であり、「ファット」ポインタ、またはメタデータを持つポインタです。ここで、メタデータはスライスの長さです。
まとめ#
- 利点
- ガベージコレクションを回避することでランタイムパフォーマンスを向上させる
- データの偶発的な「リーク」を防ぐことで予測可能性を向上させる
- ポインタは以下を通じて作成できます
- ボックス(ヒープ上のデータを所有するポインタ)
- 参照(所有しないポインタ)
- 移動と借用
- コピーできない型(Boxや String など)の変数の移動には RO 権限が必要で、移動は変数のすべての権限を排除します。このルールは移動された変数の使用を防ぎます:
- 変数を借用する(それへの参照を作成する)ことは、一時的に変数の一部の権限を削除します
- 不変の借用は不変の参照を作成し、また借用されたデータが変更または移動されることを無効にします。
- 可変の借用は可変の参照を作成し、借用されたデータが読み取られたり、書き込まれたり、移動されたりすることを無効にします
- use-after-free:不変の借用は W 権限を削除して use-after-free を回避します、
- ダブルフリー:コピーできないデータへの参照のデリファレンスは、ダブルフリーを回避するために O 権限を持ちません
第 5 章 構造体#
- Rust はコンストラクタ関数のためのキーワードを持っていません。コンストラクタ関数を定義するための慣用的な方法は、new という関連関数を作成することですが、それは言語によって強制されるものではありません。
- タプル構造体。例:
struct Color (i32,i32,i32);
- Rust は self パラメータの型を一致させるために必要なだけの参照とデリファレンスを挿入します
- Rust は API の変更に対する安定性のために Copy を自動的に導出しません。
#[derive(Copy, Clone)]
- "cannot move out of *self" のようなエラーが表示される場合、それは通常、&self や & mut self のような参照で self メソッドを呼び出そうとしているためです。Rust はダブルフリーからあなたを守っています
第 6 章 列挙型#
-
構造体よりも列挙型を使用する利点:
- 各バリアントは異なる型と関連データの量を持つことができます
- 定義した各列挙型バリアントの名前は、列挙型のインスタンスを構築する関数にもなります
- 列挙型バリアントの中に任意の種類のデータを入れることができます:文字列、数値型、または構造体など。別の列挙型を含めることもできます
-
Option 列挙型
- コンパイラは、すべてのケースを処理しているかどうかをチェックできます
- null は、現在無効または何らかの理由で存在しない値です。
- Rust には null がありませんが、値が存在するかどうかの概念をエンコードできる列挙型があります。
- 関数 Option::unwrap はselfを期待しており、arg の所有権を期待しています。しかし arg はオプションへの不変の参照であるため、オプションの所有権を提供することはできません。
-
マッチ
-
各マッチは上から下へ試されます
-
opt はプレーンな列挙型です — その型は Optionであり、&Optionのような参照ではありません。したがって、opt のマッチは s のような無視されないフィールドを移動します。
-
opt の内容を移動せずに覗きたい場合、慣用的な解決策は参照でマッチすることです:
-
if let
- if let は、値が 1 つのパターンに一致したときにコードを実行し、他のすべての値を無視するためのマッチの構文糖です。
- else に付随するコードブロックは、if let と else に相当するマッチ式の_ケースに付随するコードブロックと同じです。
第 7 章 パッケージ、クレート、モジュールを使った成長するプロジェクトの管理#
パッケージ:クレートを構築、テスト、共有するための Cargo 機能#
- 1 つ以上のクレートのバンドルで、機能のセットを提供します。
- パッケージには、好きなだけのバイナリクレートを含めることができますが、ライブラリクレートは最大で 1 つだけです。
- 外部パッケージの使用
- 標準の std ライブラリも、私たちのパッケージに外部のクレートです。std を含めるために Cargo.toml を変更する必要はありません。しかし、それを参照する必要があります use を使って、そこからアイテムを私たちのパッケージのスコープに持ち込みます。
クレート:ライブラリまたは実行可能ファイルを生成するモジュールのツリー#
- バイナリクレート:
main
という関数を持つ必要があります - ライブラリクレート:複数のプロジェクトと共有することを意図した機能を定義します。
Rustaceans が「クレート」と言うとき、彼らはライブラリクレートを指し、「ライブラリ」という一般的なプログラミング概念と同じ意味で「クレート」を使います。
モジュールと use:パスの組織、スコープ、プライバシーを制御できます#
- 用途
- 可読性と再利用の容易さのために、クレート内でコードを整理できます
- アイテムのプライバシーを制御できるようにします。モジュール内のコードはデフォルトでプライベートです
- 親と子
- すべてのアイテム(関数、メソッド、構造体、列挙型、モジュール、定数)はデフォルトで親モジュールにプライベートです。
- 親モジュール内のアイテムは、子モジュール内のプライベートアイテムを使用できませんが、子モジュール内のアイテムはその先祖モジュール内のアイテムを使用できます。
パス:構造体、関数、モジュールなどのアイテムに名前を付ける方法#
- 慣用的な方法
- use で関数の親モジュールをスコープに持ち込む
- 構造体、列挙型、その他のアイテムを use で持ち込むときは、フルパスを指定するのが慣用的です
use std::io::Result as IoResult;
- ネストされたパスを使用して、同じアイテムを 1 行でスコープに持ち込むことができます。
use std::{cmp::Ordering, io};
,use std::io::{self, Write};
第 8 章#
ベクタ#
- ベクタの最後の要素の変更を気にする理由
- ベクタの最後に新しい要素を追加することは、新しいメモリを割り当て、古い要素を新しいスペースにコピーする必要があるかもしれません
- 最初の要素への参照は解放されたメモリを指していることになります
- Vec::push はその引数を移動するため、v.push (s) を呼び出した後、s は使用できません
- ベクタがドロップされると、そのすべての内容もドロップされ、保持している整数はクリーンアップされます。
文字列#
- コンパイラは & String 引数を & str に強制変換できます
- Rust の文字列はインデックス付けをサポートしていません予期しない値を返し、すぐに発見されないバグを引き起こすのを避けるために
- Rust の視点から文字列を見るための 3 つの関連する方法
- バイト
- スカラー値
- グラフエムクラスター
- 文字列の一部を操作する最良の方法は、文字またはバイトのどちらを望むかを明示的にすることです
- &str は、それが指すバイト列が常に有効な UTF-8 であることを約束します
ハッシュマップ#
- ハッシュマップは、インデックスを使用してではなく、任意の型のキーを使用してデータを検索したいときに便利です
- Copy トレイトを実装する型(i32 など)については、値がハッシュマップにコピーされます。- String のような所有された値については、値が移動され、ハッシュマップがそれらの値の所有者になります
第 10 章#
ジェネリックデータ型#
- Rust は、ジェネリック型の期待される能力を前もって述べることを要求します
- 制限がない場合、ジェネリック型 T には能力がありません:印刷、クローン、または変更できません(ただし、ドロップできます)。
- Rust には、オブジェクト指向言語で見られるようなメソッドを特殊化するための継承のようなメカニズムはありません、
- モノモルフィゼーションは、コンパイル時に使用される具体的な型を埋め込むことによって、ジェネリックコードを特定のコードに変換するプロセスです
- const ジェネリック:
const N : usize
トレイト#
- トレイトは、特定の型が持ち、他の型と共有できる機能を定義します。
- 注意すべき制限の 1 つは、トレイトまたは型のいずれかが私たちのクレートにローカルである場合にのみ、型にトレイトを実装できることです。
- デフォルトの実装は、デフォルトの実装を持たない他のメソッドを同じトレイト内で呼び出すことができます
- パラメータ内のトレイト
fn some_function<T:Display + Clone ,U: Clone + Debug>(t:&T,u:&U) -> i32{} // where句を使った明確なトレイト境界 fn some_function<T,U>(t:&T,u:&U) -> i32 where T:Display + Clone , U: Clone + Debug {}
- 単一の型を返す場合にのみ impl Trait を使用できます
- メソッドを条件付きで実装するためのトレイト境界の使用
use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("最大のメンバーはx = {}", self.x); } else { println!("最大のメンバーはy = {}", self.y); } } }
- 他のトレイトを実装する任意の型に対してトレイトを条件付きで実装する
impl<T: Display> ToString for T { // --snip-- }
ライフタイム#
- ライフタイム注釈は、参照の生存期間を変更しません。むしろ、複数の参照のライフタイムの関係を記述し、ライフタイムに影響を与えません
- ライフタイムパラメータの名前はアポストロフィ(')で始まり、通常はすべて小文字で非常に短いです
- この関数シグネチャでライフタイムパラメータを指定するとき、渡された値や返された値のライフタイムを変更しているわけではありません。むしろ、借用チェッカーがこれらの制約に従わない値を拒否するように指定しています。
- Rust の初期バージョン(1.0 以前)では、すべての参照に明示的なライフタイムが必要でした
- コンパイラは3 つのルールを使用して、明示的な注釈がない場合の参照のライフタイムを判断します。
- コンパイラは、各入力型の各ライフタイムに異なるライフタイムパラメータを割り当てます。
- 正確に 1 つの入力ライフタイムパラメータがある場合、そのライフタイムはすべての出力ライフタイムパラメータに割り当てられます
- 複数の入力ライフタイムパラメータがあるが、そのうちの 1 つが
&self
または&mut self
である場合、これはメソッドであるため、self のライフタイムはすべての出力ライフタイムパラメータに割り当てられます。
- スタティックライフタイム
'static
- 文字列リテラルはプログラムのバイナリに直接保存されており、常に利用可能です。したがって、すべての文字列リテラルのライフタイムは
'static
です。 - 'static は「プログラム全体のために生きる」を意味し、スタティック参照の下のデータは決して解放されるべきではありません。
- 文字列リテラルはプログラムのバイナリに直接保存されており、常に利用可能です。したがって、すべての文字列リテラルのライフタイムは
- ライフタイム注釈は、参照の実際のスコープを変更しません
第 11 章#
自動生成されたテストの実行結果#
- 測定された 0 の統計は、パフォーマンスを測定するベンチマークテストのためのものです。
Doc-tests
は、ドキュメントテストの結果のためのものです
よく使うコマンド#
assert!();
assert_eq!();
assert_ne! // 2つの値が等しくない場合に合格し、等しい場合に失敗します。
テストを並行または連続して実行する#
- 複数のテストを実行するとき、デフォルトではスレッドを使用して並行して実行されます
cargo test -- --test-threads=1
cargo test -- --show-output
cargo test --help
cargo test -- --help
-
#[test] #[ignore] cargo test -- --ignored cargo test -- --include-ignored
- テスト名の一部を指定でき、その値に一致するテストはすべて実行されます。
テストの組織#
- ユニットテストは、ライブラリの異なる部分を個別にテストし、プライベートな実装の詳細をテストできます。
- ユニットテストは、ライブラリの異なる部分を個別にテストし、プライベートな実装の詳細をテストできます。
第 12 章#
- 必要な関数が複数のモジュールにネストされている場合、関数ではなく親モジュールをスコープに持ち込むことを選択しました。
- TDD(テスト駆動開発)
- 失敗するテストを書き、それが期待通りに失敗することを確認します。
- 新しいテストを通過させるために必要なだけのコードを書いたり修正したりします。
- 追加または変更したコードをリファクタリングし、テストが引き続き通過することを確認します。
- ステップ 1 から繰り返します!
第 13 章#
- Rust はクロージャの引数 / 戻り値の型を推論しますが、トップレベルの関数には推論しません
- クロージャは、環境から値を 3 つの方法でキャプチャできます
- 不変に借用する
- 可変に借用する
- 所有権を取得する。
- Fn トレイト
- FnOnce
- FnMut
- Fn
- イテレータアダプタは遅延であり、ここでイテレータを消費する必要があります。