オンライン読書アドレス:Rust 言語聖書
読書進捗:60%
開発ツール#
VSCode#
プラグイン
- rust-analyzer, Rust 言語プラグイン
 - Even Better TOML, .toml ファイルの完全な機能をサポート
 - Error Lens, エラー表示をより良くする
 - CodeLLDB, デバッガプログラム
 
RustRover#
プラグイン
設定
- コードフォーマット
 


Cargo#
cargo ツールを使用する最大の利点は、プロジェクトのさまざまな依存関係を便利で統一的かつ柔軟に管理できることです。
Cargo.toml と Cargo.lock#
Cargo.tomlはcargo特有のプロジェクトデータ記述ファイルです。プロジェクトのすべてのメタ設定情報を保存しており、Rust 開発者が Rust プロジェクトを期待通りに構築、テスト、実行したい場合、合理的な方法でCargo.tomlを構築する必要があります。Cargo.lockファイルはcargoツールが同じプロジェクトのtomlファイルに基づいて生成したプロジェクト依存詳細リストであるため、一般的には変更する必要はなく、Cargo.tomlファイルに従って作業すればよいです。
Cargo.toml では、さまざまな依存段落を通じてプロジェクトのさまざまな依存関係を記述します:
[dependencies]
rand = "0.3"
hammer = { version = "0.5.0"} // Rust 公式リポジトリ crates.io に基づき、バージョン指定で記述
color = { git = "https://github.com/bjz/color-rs" } // プロジェクトソースコードの git リポジトリアドレスに基づき、URL で記述
geometry = { path = "crates/geometry" } // ローカルプロジェクトの絶対パスまたは相対パスに基づき、Unix スタイルのパスで記述
- 
Cargo.toml ファイル内容
Cargo.tomlファイルは Rust プロジェクトで依存関係、コンパイルオプション、メタデータなどの情報を構成および管理するための設定ファイルです。以下はCargo.tomlファイルの一般的な部分とその用途です:1.
[package]#この部分はパッケージの基本情報を定義し、名前、バージョン、著者、説明などを含みます。
[package] name = "my_project" # プロジェクト名 version = "0.1.0" # プロジェクトバージョン edition = "2021" # Rust 言語バージョン authors = ["Your Name <[email protected]>"] # 著者 description = "A brief description of the project" # プロジェクト説明 license = "MIT" # プロジェクト使用ライセンス repository = "<https://github.com/yourusername/my_project>" # プロジェクトソースコードリポジトリ2.
[dependencies]#この部分では、プロジェクトが依存するライブラリとそのバージョンを定義します。
[dependencies] serde = "1.0" # serde ライブラリを依存として追加 rand = { version = "0.8", features = ["std"] } # バージョンと追加機能を指定3.
[dev-dependencies]#この部分は開発環境で必要な依存関係を定義し、これらの依存関係は通常テストや開発ツールでのみ使用されます。
[dev-dependencies] tokio = { version = "1", features = ["full"] } # 開発段階でのみ使用4.
[build-dependencies]#この部分はビルドスクリプト (
build.rs) 内の依存関係を定義します。[build-dependencies] cc = "1.0" # ネイティブコードをビルドするためのライブラリ5.
[features]#プロジェクトのオプション機能を定義します。これらの機能はユーザーが選択的に有効または無効にできます。
[features] default = ["serde"] # デフォルトで有効な機能 extras = ["tokio"] # オプション機能6.
[package.metadata]#この部分は通常、カスタムツールの設定に使用され、その内容と構造はツールによって異なります。
[package.metadata] docs = { rsdoc = true } # カスタムドキュメント生成器の設定7.
[workspace]#プロジェクトがワークスペース(複数のサブプロジェクトを含む)である場合、この部分を使用してワークスペースの構造を定義します。
[workspace] members = ["project1", "project2"] # ワークスペース内のメンバプロジェクト8.
[patch]と[replace]#依存関係のバージョンを置き換えたり修正したりするために使用され、通常は依存関係の競合や開発中に使用されます。
[patch.crates-io] serde = { git = "<https://github.com/serde-rs/serde>", branch = "master" } [replace] foo = { path = "../foo" } # 依存ライブラリを置き換え9.
[profile]#この部分では、異なるコンパイル設定(
dev,releaseなど)に対してカスタムコンパイルオプションを設定できます。[profile.dev] opt-level = 1 # 開発モードで使用される最適化レベル [profile.release] opt-level = 3 # リリースモードで使用される最適化レベル10.
[badges]#ドキュメント生成ツールや他のサービスでステータスバッジ(ビルドステータス、コードカバレッジなど)を表示するために使用されます。
[badges] travis-ci = { repository = "rust-lang/crates.io" } # Travis CI バッジを表示これは
Cargo.tomlファイルの一般的な部分とその用途です。プロジェクトの複雑さやニーズに応じて、ファイルの内容はさらに豊富またはシンプルになることがあります。 
ミラー加速#
依存関係のダウンロードが遅い? - Rust 言語聖書 (Rust Course)
[http]
check-revoke = false
# proxy = "http://x.x.x.x:8888" // 仮想マシン内でネットワークに接続されていない開発時にプロキシとして使用
[source.crates-io]
replace-with = 'rsproxy'
[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"
[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"
[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"
[net]
git-fetch-with-cli = true
ジェネリクスとトレイト#
ジェネリクスは多態性の一種です。ジェネリクスの主な目的は、プログラマーにプログラミングの便利さを提供し、コードの冗長性を減らし、同時に言語自体の表現力を大幅に豊かにすることです。
- 列挙型
 
enum Option<T> {
		Some(T),
		None,
}
- 構造体
 
struct Point<T> {
    x: T,
    y: T,
}
- メソッド
 
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
fn main() {
    let p = Point { x: 5, y: 10 };
    println!("p.x = {}", p.x());
}
ジェネリクスの性能#
Rust におけるジェネリクスはゼロコストの抽象であり、ジェネリクスを使用する際に性能の問題を心配する必要はありませんが、コンパイル速度が低下し、最終的に生成されるファイルのサイズが大きくなります。
Rust はコンパイル時にジェネリックコードの単相化(monomorphization) を行うことで効率を保証します。単相化は、コンパイル時に使用される具体的な型で一般的なコードを特定のコードに変換するプロセスです。
コンパイラが行う作業は、私たちがジェネリック関数を作成する手順と正確に逆であり、コンパイラはすべてのジェネリックコードが呼び出される場所を探し、具体的な型に対してコードを生成します。
トレイトの多重制約#
pub fn notify(item: &(impl Summary + Display)) {} // シンタックスシュガー形式
pub fn notify<T: Summary + Display>(item: &T) {} // トレイト制約の形式
Where 制約
トレイト制約が多くなると、関数のシグネチャが複雑になります:
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
{}
動的配列#
https://doc.rust-lang.org/std/collections/
作成#
let mut v1 = Vec::with_capacity(10);
v1.extend([1, 2, 3]);    // v にデータを追加
println!("Vector の長さは: {}, 容量は: {}", v.len(), v.capacity());
よく使うメソッド#
let mut v =  vec![1, 2];
assert!(!v.is_empty());         // v が空でないか確認
v.reserve(100);                 // v の容量を調整し、少なくとも 100 の容量を持つようにする
v.insert(2, 3);                 // 指定したインデックスにデータを挿入、インデックス値は v の長さを超えてはいけない、 v: [1, 2, 3] 
assert_eq!(v.remove(1), 2);     // 指定位置の要素を削除し、返す, v: [1, 3]
assert_eq!(v.pop(), Some(3));   // v の末尾の要素を削除し、返す、v: [1]
assert_eq!(v.pop(), Some(1));   // v: []
assert_eq!(v.pop(), None);      // pop メソッドが返すのは Option 列挙値であることを忘れないでください
v.clear();                      // v をクリア、v: []
let mut v1 = [11, 22].to_vec(); // append 操作は v1 のデータをクリアし、可変宣言を追加
v.append(&mut v1);              // v1 のすべての要素を v に追加, v1: []
v.truncate(1);                  // 指定された長さに切り詰め、多すぎる要素が削除される, v: [11]
v.retain(|x| *x > 10);          // 条件を満たす要素を保持し、条件を満たさない要素を削除
let mut v = vec![11, 22, 33, 44, 55];
// 指定範囲の要素を削除し、削除された要素のイテレータを取得, v: [11, 55], m: [22, 33, 44]
let mut m: Vec<_> = v.drain(1..=3).collect();    
let v2 = m.split_off(1);        // 指定インデックスで二つの vec に分割, m: [22], v2: [33, 44]
let v2 = vec![11, 22, 33, 44, 55]; // スライスを使用して配列を切り取る
let slice = &v2[1..=3];
assert_eq!(slice, &[22, 33, 44]);
ソート#
fn main() {
    let mut vec = vec![1.0, 5.6, 10.3, 2.0, 15f32];    
    vec.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
    assert_eq!(vec, vec![1.0, 2.0, 5.6, 10.3, 15f32]);
}
構造体のソート
// 年齢で降順にソートする比較関数をカスタマイズ
people.sort_unstable_by(|a, b| b.age.cmp(&a.age));
ソートには Ord トレイトを実装する必要があります。構造体がこのトレイトを実装している場合、カスタム比較関数を必要としません。ただし、Ord を実装するには、Ord、Eq、PartialEq、PartialOrd などの属性を実装する必要があり、これらの属性は derive できます:
#[derive(Debug, Ord, Eq, PartialEq, PartialOrd)] // derive 属性
struct Person {
    name: String,
    age: u32,
}
impl Person {
    fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
}
fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("Al".to_string(), 30),
        Person::new("John".to_string(), 1),
        Person::new("John".to_string(), 25),
    ];
    people.sort_unstable();
    println!("{:?}", people);
}
// => [Person { name: "Al", age: 30 }, Person { name: "Al", age: 60 }, Person { name: "John", age: 1 }, Person { name: "John", age: 25 }, Person { name: "Zoe", age: 25 }]
derive のデフォルト実装は属性の順序に基づいて比較を行います。上記の例では、Person の name 値が同じ場合、age を使用して比較します。
HashMap#
作成#
fn main() {
	use std::collections::HashMap;
	
	let teams_list = vec![
	    ("中国队".to_string(), 100),
	    ("美国队".to_string(), 10),
	    ("日本队".to_string(), 50),
	];
	
	let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
	println!("{:?}",teams_map)
}
ライフタイム🌟#
懸垂ポインタ#
ライフタイムの主な役割は、懸垂参照を避けることであり、これによりプログラムが本来参照すべきでないデータを参照することを防ぎます。
{
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {}", r);
}
構文#
ライフタイムの構文は ' で始まり、名前は通常単独の小文字の文字であり、一般的に 'a をライフタイムの名前として使用します。参照型のパラメータの場合、ライフタイムは参照記号 & の後に位置し、ライフタイムと参照パラメータを空白で区切ります:
&i32        // 参照
&'a i32     // 明示的なライフタイムを持つ参照
&'a mut i32 // 明示的なライフタイムを持つ可変参照
関数シグネチャ
ライフタイムパラメータを使用する場合、最初に <'a> を宣言する必要があります。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
静的ライフタイム
Rust には非常に特別なライフタイムがあり、それは 'static です。このライフタイムを持つ参照はプログラム全体と同じくらい長く生きることができます。
以前に学んだ文字列リテラルについて言及したように、これは Rust のバイナリファイルにハードコーディングされているため、これらの文字列変数はすべて 'static のライフタイムを持っています:
let s: &'static str = "私には特に優れた点はありませんが、長生きしています、へへ";
ライフタイムの消去#
始める前に注意すべき点がいくつかあります:
- 
消去ルールは万能ではなく、コンパイラが何かが正しいと確信できない場合、それは不正と見なされ、手動でライフタイムを注釈する必要があります。
 - 
関数またはメソッド内で、パラメータのライフタイムは
入力ライフタイムと呼ばれ、戻り値のライフタイムは出力ライフタイムと呼ばれます。 - 
三つの消去ルール
コンパイラは三つの消去ルールを使用して、どのシナリオで明示的にライフタイムを注釈する必要がないかを決定します。第一のルールは入力ライフタイムに適用され、第二と第三のルールは出力ライフタイムに適用されます。コンパイラが三つのルールのいずれも適用できないと判断した場合、エラーが発生し、手動でライフタイムを注釈する必要があることが通知されます。
- 
各参照パラメータは独自のライフタイムを持つ
例えば、参照パラメータの関数には一つのライフタイム注釈があります:
fn foo<'a>(x: &'a i32)、二つの参照パラメータには二つのライフタイム注釈があります:fn foo<'a, 'b>(x: &'a i32, y: &'b i32)、このように続きます。 - 
入力ライフタイムが一つだけの場合(関数パラメータに参照型が一つだけの場合)、そのライフタイムはすべての出力ライフタイムに割り当てられます、つまりすべての戻り値のライフタイムはその入力ライフタイムに等しくなります。
例えば関数
fn foo(x: &i32) -> &i32では、xパラメータのライフタイムは自動的に戻り値&i32に割り当てられるため、この関数はfn foo<'a>(x: &'a i32) -> &'a i32と同等です。 - 
複数の入力ライフタイムが存在し、その中の一つが
&selfまたは&mut selfである場合、&selfのライフタイムがすべての出力ライフタイムに割り当てられます&self形式のパラメータを持つことは、その関数がメソッドであることを示し、このルールによりメソッドの使用の便利さが大幅に向上します。 
 - 
 
複雑な例:ジェネリクス、トレイト制約#
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>( // ライフタイムとジェネリクスを宣言
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann); // フォーマット {} を使用して ann を出力するため、Display トレイトを実装する必要があります。
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
Fn トレイトでクロージャのライフタイムを解決#
fn main() {
   let closure_slision = fun(|x: &i32| -> &i32 { x });
   assert_eq!(*closure_slision(&45), 45);
   // 通過しました!
}
fn fun<T, F: Fn(&T) -> &T>(f: F) -> F {
   f
}
NLL (非構文的ライフタイム)#
以前に参照と借用の章でこの概念について説明しました。簡単に言えば:参照のライフタイムは通常、借用が始まってから作用域が終了するまで続くべきですが、このルールは複数の参照が共存する状況をより複雑にします:
fn main() {
   let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // 新しいコンパイラでは、r1,r2 の作用域はここで終了します
    let r3 = &mut s;
    println!("{}", r3);
}
上記のルールに従うと、このコードはエラーになります。なぜなら、r1 と r2 の不変参照は main 関数が終了するまで持続し、その範囲内で r3 の可変参照を借用しているため、借用のルールに違反します:複数の不変借用か、1 つの可変借用のいずれかです。
幸いなことに、このルールは 1.31 バージョンから NLL が導入され、次のように変更されました:参照のライフタイムは借用が始まってから最後の使用場所まで続きます。
最新のルールに従って、上記のコードを再分析します。r1 と r2 の不変借用は println! の後で使用されなくなるため、ライフタイムもそれに伴って終了し、r3 の可変借用は借用のルールに違反しなくなります。
再借用#
https://course.rs/advance/lifetime/advance.html#reborrow - 再借用
- todo
 
エラー処理#
パニック#
https://course.rs/basic/result-error/panic.html#panic - 原理剖析
バックトレーススタック展開
プログラムが panic をスローする前にどの呼び出しがあったかを知りたい場合、指示に従って RUST_BACKTRACE=1 cargo run または $env:RUST_BACKTRACE=1 ; cargo run を使用してプログラムを実行します。
panic 時の二つの終了方法
panic! が発生した場合、プログラムは終了プロセスを処理するための二つの方法を提供します:スタック展開と直接終了。
デフォルトの方法は スタック展開 であり、これは Rust がスタック上のデータと関数呼び出しを遡ることを意味します。したがって、より多くの事後処理が必要になります。利点は、十分なエラーメッセージとスタック呼び出し情報を提供し、問題の振り返りが容易になることです。直接終了 は、データをクリーンアップせずにプログラムを直接終了し、事後処理をオペレーティングシステムに任せることを意味します。
ほとんどのユーザーにとって、デフォルトの選択を使用するのが最善ですが、最終的にコンパイルされたバイナリ実行ファイルのサイズを気にする場合は、直接終了の方法を使用することを検討できます。たとえば、以下の設定を Cargo.toml ファイルに変更して、release モードで panic が発生した場合に直接終了するようにします:
[profile.release]
panic = 'abort'
パッケージとモジュール#
アイテムの再エクスポート#
外部のモジュールアイテム A が現在のモジュールにインポートされると、その可視性は自動的にプライベートに設定されます。 他の外部コードが私たちのモジュールアイテム A を参照できるようにしたい場合、再エクスポートする必要があります:
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
上記のように、pub use を使用することで実現できます。ここで use は hosting モジュールを現在のスコープにインポートし、pub はそのインポートされた内容を再度可視化します。
内部の実装詳細を隠したり、特定の目的でコードを整理したりしたい場合は、pub use を使用して再エクスポートできます。たとえば、特定のモジュールを使用して外部 API を提供する場合、そのモジュールは他のモジュールの API をインポートし、再エクスポートすることができます。最終的に、ユーザーにとってはすべての API が一つのモジュールから統一的に提供されます。
コメントとドキュメント#
コードコメント#
// 行コメント
/**
	ブロックコメント
*/
ドキュメントコメント#
/// `add_one` は指定された値に1を加えます
///
/// # 例
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
/** `add_two` は指定された値に2を加えます
let arg = 5;
let answer = my_crate::add_two(arg);
assert_eq!(7, answer);
*/
pub fn add_one(x: i32) -> i32 {
    x + 1
}
一般的なドキュメントタイトル
ドキュメントコメントでは # 例 というタイトルの他にもいくつか一般的なものがあり、プロジェクトに応じて使用できます:
- パニック:関数が発生する可能性のある異常状況、これにより関数を呼び出す人が事前に回避できます。
 - エラー:発生する可能性のあるエラーと、どのような状況でエラーが発生するかを説明し、呼び出し側が異なるエラーに対して異なる処理を行うのに役立ちます。
 - 安全性:関数が 
unsafeコードを使用する場合、呼び出し側はunsafeコードブロックが正常に動作するためのいくつかの使用条件に注意する必要があります。 
パッケージおよびモジュールレベルのコメント#
行コメント //! とブロックコメント /*! ... */ に分かれます。
ドキュメントの表示#
cargo doc 
cargo doc --open
ドキュメントテスト#
https://course.rs/basic/comment.html# 文档测试 doc-test
テストを保持し、ドキュメントを隠す
ある時、ドキュメントテストの機能を保持したいが、特定のテストケースの内容をドキュメントから隠したい場合:
/// ```
/// # // # で始まる行はドキュメントに隠されますが、ドキュメントテストでは実行されます
/// # fn try_main() -> Result<(), String> {
/// let res = world_hello::compute::try_div(10, 0)?;
/// # Ok(()) // try_main から戻る
/// # }
/// # fn main() {
/// #    try_main().unwrap();
/// #
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("ゼロ除算"))
    } else {
        Ok(a / b)
    }
}
上記のドキュメントコメントでは、ユーザーに見せたくない内容を隠すために # を使用していますが、テストケースの実行には影響しません。最終的にユーザーは隠されていない行 let res = world_hello::compute::try_div(10, 0)?; のみを見ることになります:
フォーマット出力#
https://course.rs/basic/formatted-output.html
{} と#
他の言語で一般的に使用される %d、%s とは異なり、Rust は特異的に {} をフォーマットプレースホルダーとして選択しました。この選択は非常に正しいことが証明され、ユーザーは特定の型に特定のプレースホルダーを選択する必要がなく、統一的に {} を使用するだけで済みます。残りの型推論などの詳細は Rust に任せておけばよいのです。
{} と似たように、{:?} もプレースホルダーです:
{}はstd::fmt::Displayトレイトを実装した型に適用され、より優雅で親しみやすい方法でテキストをフォーマットします。たとえば、ユーザーに表示する場合などです。{:?}はstd::fmt::Debugトレイトを実装した型に適用され、デバッグシーンで使用されます。
二つの選択は非常にシンプルです。コードを書く際にデバッグが必要な場合は {:?} を使用し、それ以外のシーンでは {} を選択します。
**Display トレイト **
ほとんどの型が Debug を実装しているのとは異なり、Display トレイトを実装している Rust 型はそれほど多くありません。通常、私たちは望むフォーマット方法をカスタマイズする必要があります:
let i = 3.1415926;
let s = String::from("hello");
let v = vec![1, 2, 3];
let p = Person {
    name: "sunface".to_string(),
    age: 18,
};
println!("{}, {}, {}, {}", i, s, v, p);
実行後、v と p はコンパイルできないことがわかります。なぜなら、Display トレイトを実装していないからです。しかし、Debug のように Display を派生させることはできず、別の方法を探す必要があります:
{:?}または{:#?}を使用- カスタム型に 
Displayトレイトを実装 - 外部型に 
Displayトレイトを実装するためにnewtypeを使用 
以下にこれら三つの方法を見ていきましょう。
{:#?} は {:?} とほぼ同じですが、唯一の違いは内容をより美しく出力できることです:
// {:?}
[1, 2, 3], Person { name: "sunface", age: 18 }
// {:#?}
[
    1,
    2,
    3,
], Person {
    name: "sunface",
}
したがって、Display をサポートしていない型に対しては {:#?} を使用してフォーマットすることを検討できます。理論的には、デバッグ出力により適しています。
カスタム型に Display を実装する トレイト **
もしあなたの型が現在のスコープに定義されている場合、Display トレイトを実装することでフォーマット出力に使用できます:
struct Person {
    name: String,
    age: u8,
}
use std::fmt;
impl fmt::Display for Person {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "大佬在上,请受我一拜,小弟姓名{},年芳{},家里无田又无车,生活苦哈哈",
            self.name, self.age
        )
    }
}
fn main() {
    let p = Person {
        name: "sunface".to_string(),
        age: 18,
    };
    println!("{}", p);
}
このように、Display トレイトの fmt メソッドを実装することで、カスタム構造体 Person にカスタム出力を追加できます。