🐣 Rust swing
これとこれとこれ
流石に動かす
- 低レベルコードは微細なバグを抱える傾向があるが、Rustはバグのあるコードをコンパイルしない(静的解析を行う)
- Cargoは、ビルドシステム兼パッケージマネジャ
- コードのビルド、コードが依存するライブラリのダウンロード、それらのライブラリのビルドを行う
- CargoはRustとともにインストールされる
-
npm init -y
みたいにcargo new pj_name
でプロジェクト作成できる
- RustfmtはRustのフォーマッタ
- rustfmtはrustcなどと同様に標準的にrustに組み込まれている
- rustupはRustのバージョンマネージャ
Rustを学ぶ過程で重要な部分は、コンパイラが表示するエラーメッセージを読む方法を学ぶことです
Cargoについて
まとめ
-
cargo new
を使ってプロジェクトを作成できる -
cargo build
を使ってプロジェクトをビルドできる -
cargo run
を使うとプロジェクトのビルドと実行を1ステップで行える -
cargo check
を使うとバイナリを生成せずにプロジェクトをビルドして、エラーがないか確認できる - Cargoは、ビルドの成果物をコードと同じディレクトリに保存するのではなく、
target/debug
ディレクトリに格納する -
cargo build --release
で最適化した状態でプロダクションビルド- プロダクションビルドの最適化=実行速度向上, but プログラムのコンパイルにかかる時間が長く
- コードの実行時間をベンチマークするならこっち〜!
-
cargo build
は開発用ビルド。実行速度は遅い, but プログラムのコンパイルにかかる時間は短い
-
cargo update
: 現在のCargo.lockを無視して、Cargo.tomlファイル内の全ての指定に適合する最新バージョンを算出→成功したらCargoはそれらのバージョンをCargo.lockファイルに記録するので依存の生合成はとれたままアップグレードはされる
memo
- 設定フォーマットはtomlファイル
[package]
# Cargoがプログラムをコンパイルするのに必要となる設定情報
name = "hello_world"
version = "0.1.0"
edition = "2021" #使用するRustのエディション
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# ここにプロジェクトの依存を列挙する
# Rustではパッケージのことを**クレート**とよぶ。
- Cargoはソースファイルがsrcディレクトリにあることを期待
- プロジェクトの最上位のディレクトリは、READMEファイル、ライセンス情報、設定ファイル、その他のコードに関係しないものだけ
- Cargoを使用するプロジェクトへと変換するには、プロジェクトのコードをsrcディレクトリに移動し、適切なCargo.tomlファイルを作成すればいい
cargo build
で実行する
プロジェクトを- cargoプロジェクトは
cargo build
でビルドできる (ビルド) -
target/debug/project_name
にバイナリが生成される-
./target/debug/project_name
で実行
-
-
cargo run
(ビルドから実行まで) -
cargo check
(コードがコンパイル可能かチェック)- プログラムを書きながら定期的にcargo checkを実行し、コンパイルできるか確かめるのに便利!
Cargoのレジストリはこれ
初めてのビルドでCargo.lockにexactバージョンが書き込まれる
ビルドが成功してCargo.lockが生成されたということは、現在の依存関係は解決されて正しいということ
手動で明示的にバージョンをアップデートプロジェクトはビルドの際に初回ビルドのCargo.lockを参照し続ける
cargo update
について詳しい動き
0.8.4
と0.9.0
の二つがリリースされていたとする
→cargo updateを実行
→0.8.4
->メジャーバージョンのアップデートは自動で行わない!!
0.9.0
にしたいときはCargo.tomlを書き換える必要
こんな感じでエラーハンドリングしていないことを教えてくれる
SemVerについての説明
Cargoはセマンティックバージョニング(SemVerと呼ばれることもあります)を理解しており、これはバージョンナンバーを記述するための標準です。 0.8.3という数字は実際には^0.8.3の省略記法で、0.8.3以上0.9.0未満の任意のバージョンを意味します。 Cargoはこれらのバージョンを、バージョン0.8.3と互換性のある公開APIを持つものとみなします。 この仕様により、この章のコードが引き続きコンパイルできるようにしつつ、最新のパッチリリースを取得できるようになります。 0.9.0以降のバージョンは、以下の例で使用しているものと同じAPIを持つことを保証しません。
SemVerのおかげですべてのコードに互換性があるようになる
cargo doc --open
でプロジェクト内で使用されているクレートのドキュメントを開くことができる!!!すごい!!!
クレートのどのトレイトをuseするか、どのメソッドを呼び出すかを知るために使えそう
変数のシャドーイング
Rustではguessの前の値を新しい値で覆い隠す(shadowする)ことが許されているのです。 シャドーイング(shadowing)は、guess_strとguessのような重複しない変数を二つ作る代わりに、guessという変数名を再利用させてくれるのです。 これについては第3章で詳しく説明しますが、今のところ、この機能はある型から別の型に値を変換するときによく使われる
🔴変数名はsnake_caseで表す!
fn main() {
// x の型を推論
let x = 13;
println!("{}", x);
// x の型を指定
let x: f64 = 3.14159;
println!("{}", x);
// 宣言の後で初期化(あまり使われません)
let x;
x = 0;
println!("{}", x);
}
型
- ブール型 - bool は true または false を表します
- 符号なし整数型 - u8 u32 u64 u128 は正の整数を表します
- 符号付き整数型 - i8 i32 i64 i128 は正と負の整数を表します
- 数値型は、数値の最後に型を付加することで明示的に指定できます。(例: 13u32, 2u8)
- ポインタサイズ整数型 - usize isize はメモリ内のインデックスとサイズを表します
- 浮動小数点数型 - f32 f64
- タプル型 - (value, value, ...) スタック上の固定された値の組を渡すために使用されます
- 配列型 - コンパイル時に長さが決まる同じ要素のコレクション
- スライス型 - 実行時に長さが決まる同じ要素のコレクション
- str(string slice) - 実行時に長さが決まるテキスト
同じ数値型でも型が正確に同じでなければならない
fn main() {
let a = 13u8;
let b = 7u32;
let c = a as u32 + b; // 🔴このようにas で型をキャストする
println!("{}", c);
let t = true;
println!("{}", t as u8);
}
定数
- 定数は明示的な型指定が必要
- 定数名には大文字のスネークケース SCREAMING_SNAKE_CASE
const PI: f32 = 3.14159;
fn main() {
println!(
"ゼロからアップル {} を作るには、まず宇宙を創造する必要があります。",
PI
);
}
配列
- データ要素がすべて同じ型
- 固定長
- データ型は [T;N] であり、T は要素の型、N はコンパイル時に決まる固定長
- [x]で要素を取得することができる(※x は取り出そうとする要素の usize 型のインデックス(0 始まり))
タプル
- 複数の値を持てる
- タプルの要素はインデックス番号で参照
- ()版のオブジェクト{}的な?
- 空のタプルは ()でunitと呼ばれる
- 関数に戻り値の型が指定されていない場合、unit と呼ばれる空のタプル()を返す
fn make_nothing() -> () {
return ();
}
// 戻り値は () と推論
fn make_nothing2() {
// この関数は戻り値が指定されないため () を返す
}
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}
fn main() {
// 戻り値をタプルで返す
let result = swap(123, 321);
println!("{} {}", result.0, result.1);
// タプルを2つの変数に分解
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b);
}
構文
loop
fn main() {
let mut x = 0;
loop {
x += 1;
if x == 42 {
break;
}
}
println!("{}", x);
}
while
fn main() {
let mut x = 0;
while x != 42 {
x += 1;
}
}
for
- 強力なイテレータを持つ
-
..
演算子は、開始番号から終了番号の手前までの数値を生成するイテレータを作成します。 -
..=
演算子は、開始番号から終了番号までの数値を生成するイテレータを作成します。
fn main() {
for x in 0..5 {
println!("{}", x);
}
for x in 0..=5 {
println!("{}", x);
}
}
match
-
switch
的な
fn main() {
let x = 42;
match x {
0 => {
println!("found zero");
}
// 複数の値にマッチ
1 | 2 => {
println!("found 1 or 2!");
}
// 範囲にマッチ
3..=9 => {
println!("found a number 3 to 9 inclusively");
}
// マッチした数字を変数に束縛
matched_num @ 10..=100 => {
println!("found {} number between 10 to 100!", matched_num);
}
// どのパターンにもマッチしない場合のデフォルトマッチが必須
_ => {
println!("found something else!");
}
}
}
return せずに戻り値を返せる
関数・if ・ブロックスコープなどで;
セミコロンで終わらない値の時は、戻り値として扱われる
fn example() -> i32 {
let x = 42;
// Rust の三項式
let v = if x < 42 { -1 } else { 1 };
println!("if より: {}", v);
let food = "ハンバーガー";
let result = match food {
"ホットドッグ" => "ホットドッグです",
// 単一の式で値を返す場合、中括弧は省略可能
_ => "ホットドッグではありません",
};
println!("食品の識別: {}", result);
let v = {
// ブロックのスコープは関数のスコープから分離されている
let a = 1;
let b = 2;
a + b
};
println!("ブロックより: {}", v);
// Rust で関数の最後から値を返す慣用的な方法
v + 4
}
fn main() {
println!("関数より: {}", example());
}
struct
- interface的な?
- 定義はメモリ上で隣合うデータの配置をコンパイラに伝える設計図の様なもの
struct SeaCreature {
// String は構造体である。
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
スタティックメソッド
- スタティックメソッド - ある型そのものに紐付き、演算子 :: で呼び出せます。
- インスタンスメソッド - ある型のインスタンスに紐付き、演算子 . で呼び出せます。
fn main() {
// スタティックメソッドでStringインスタンスを作成する。
let s = String::from("Hello world!");
// インスタンスを使ってメソッド呼び出す。
println!("{} is {} characters long.", s, s.len());
}
メモリ空間
- データメモリ - 固定長もしくは スタティック (例: プログラムのライフサイクルで常に存在するもの)なデータ。 プログラム内の文字列(例: ‘Hello World’)、 この文字列のキャラクタは読み取りにしか使えないため、この領域に入ります。 コンパイラはこういったデータに対してチューニングをしており、メモリ上の位置はすでに知られていてかつ固定であるため、非常に速く使うことができます。
- スタックメモリ - 関数内で宣言された変数。 関数が呼び出されている間は、メモリ上の位置は変更されることがないため、コンパイラからするとチューニングができるので、スタックメモリも非常に速くデータにアクセスできます。
- ヒープメモリ - プログラムの実行時に作られるデータ。 このメモリにあるデータは追加、移動、削除、サイズの調節などの操作が許されています。動的であるため、遅いと思われがちですが、 これによりメモリの使い方に柔軟性を生み出すことができます。**データをヒープメモリに入れることをアロケーション(allocation)といい、データをヒープメモリから削除することはディアロケーション(deallocation)**と言います。
struct SeaCreature {
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
// SeaCreatureのデータはスタックに入ります。
let ferris = SeaCreature { // スタックメモリ
// String構造体もスタックに入りますが、
// ヒープに入るデータの参照アドレスが一つ入ります。
animal_type: String::from("crab"),
name: String::from("Ferris"), // データメモリ
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
let sarah = SeaCreature {
animal_type: String::from("octopus"),
name: String::from("Sarah"),
arms: 8,
legs: 0,
weapon: String::from("none"),
};
println!(
"{} is a {}. They have {} arms, {} legs, and a {} weapon",
ferris.name, ferris.animal_type, ferris.arms, ferris.legs, ferris.weapon
);
println!(
"{} is a {}. They have {} arms, and {} legs. They have no weapon..",
sarah.name, sarah.animal_type, sarah.arms, sarah.legs
);
}
enum
enumで型を生成
列挙型はキーワード enum で新しい型を生成することができ、この型はいくつかのタグ付された値を持つことができます。
match は保有する全ての列挙値を処理する手助けすることができ、コードの品質を維持することもできます。
#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます。
enum Species {
Crab,
Octopus,
Fish,
Clam
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: String,
}
fn main() {
let ferris = SeaCreature {
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("claw"),
};
match ferris.species { // ferris.speciesの全ての列挙値を処理する
Species::Crab => println!("{} is a crab",ferris.name),
Species::Octopus => println!("{} is a octopus",ferris.name),
Species::Fish => println!("{} is a fish",ferris.name),
Species::Clam => println!("{} is a clam",ferris.name),
}
}
データを持つenum
🔴enumはタグみたいな感じ
🔴でもそのタグと値を紐づけることもできる
#![allow(dead_code)] // この行でコンパイラのwaringsメッセージを止めます。
enum Species { Crab, Octopus, Fish, Clam }
enum PoisonType { Acidic, Painful, Lethal }
enum Size { Big, Small }
enum Weapon { // 🔴WeaponはClaw, Poison, Noneを持つ
Claw(i32, Size), // 🔴 でもそのタグと値を紐づけることもできる
Poison(PoisonType),
None
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}
fn main() {
// SeaCreatureのデータはスタックに入ります。
let ferris = SeaCreature {
// String構造体もスタックに入りますが、
// ヒープに入るデータの参照アドレスが一つ入ります。
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small), // 🔴 でもそのタグと値を紐づけることもできる
};
match ferris.species {
Species::Crab => {
match ferris.weapon {
Weapon::Claw(num_claws,size) => {// 🔴 でもそのタグと値を紐づけることもできる
let size_description = match size {
Size::Big => "big",
Size::Small => "small"
};
println!("ferris is a crab with {} {} claws", num_claws, size_description)
},
_ => println!("ferris is a crab with some other weapon")
}
},
_ => println!("ferris is some other animal"),
}
}
ジェネリクス
- TSのそれみたいに、型を変数としておき、インスタンス生成時に確定させることができるもの
- struct や enum を部分的に定義するのに使えるとも言える
// 部分的に定義された構造体型
struct BagOfHolding<T> {
item: T,
}
fn main() {
// 注意: ジェネリック型を使用すると、型はコンパイル時に作成される。
// ::<> (turbofish) で明示的に型を指定
let i32_bag = BagOfHolding::<i32> { item: 42 };
let bool_bag = BagOfHolding::<bool> { item: true };
// ジェネリック型でも型推論可能
let float_bag = BagOfHolding { item: 3.14 };
// 注意: 実生活では手提げ袋を手提げ袋に入れないように
let bag_in_bag = BagOfHolding {
item: BagOfHolding { item: "boom!" },
};
println!(
"{} {} {} {}",
i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
);
}
::<T>で型を代入できるのだが、この書き方をturbofishという
Option<T>というNoneまたはSome(T)を取れる列挙型がある
// 部分的に定義された構造体型
struct BagOfHolding<T> {
// パラメータ T を渡すことが可能
// OptionはNoneかSome(T)を取ることができる
item: Option<T>,
}
fn main() {
// 注意: i32 が入るバッグに、何も入っていません!
// None からは型が決められないため、型を指定する必要があります。
let i32_bag = BagOfHolding::<i32> { item: None };
if i32_bag.item.is_none() {
println!("バッグには何もない!")
} else {
println!("バッグには何かある!")
}
let i32_bag = BagOfHolding::<i32> { item: Some(42) };
if i32_bag.item.is_some() {
println!("バッグには何かある!")
} else {
println!("バッグには何もない!")
}
// match は Option をエレガントに分解して、
// すべてのケースが処理されることを保証できます!
match i32_bag.item {
Some(v) => println!("バッグに {} を発見!", v),
None => println!("何も見付からなかった"),
}
}
Result型
- OK<T>, Error<E>を持つ
- T, E複数の型変数をResultに与えてOk, Errorの方を定義
enum Result<T, E> {
Ok(T),
Err(E),
}
fn do_something_that_might_fail(i:i32) -> Result<f32,String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() {
let result = do_something_that_might_fail(12);
// match は Result をエレガントに分解して、
// すべてのケースが処理されることを保証できます!
match result {
Ok(v) => println!("発見 {}", v),
Err(e) => println!("Error: {}",e),
}
}
- mainもResult型で返せる。mainの処理で何かエラーが起きた時もResult型でエラーを返せる
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
// main は値を返しませんが、エラーを返すことがあります!
fn main() -> Result<(), String> {
let result = do_something_that_might_fail(12);
match result {
Ok(v) => println!("発見 {}", v),
Err(_e) => {
// エラーをうまく処理
// 何が起きたのかを説明する新しい Err を main から返します!
return Err(String::from("main で何か問題が起きました!"));
}
}
// Result の Ok の中にある unit 値によって、
// すべてが正常であることを表現していることに注意してください。
Ok(())
}
Result型の簡潔な書き方
do_something_that_might_fail()?
***
match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
if i == 42 {
Ok(13.0)
} else {
Err(String::from("正しい値ではありません"))
}
}
fn main() -> Result<(), String> {
// コードが簡潔なのに注目!
let v = do_something_that_might_fail(42)?;
// 👇のコードと等価!
// match do_something_that_might_fail(42) {
// Ok(val) => val,
// Err(err) => return Err(e),
// }
println!("発見 {}", v);
Ok(())
}
Vector可変長配列
- Vecは可変調のリスト
fn main() {
// 型を明示的に指定
let mut i32_vec = Vec::<i32>::new(); // turbofish <3 // ::method_name()はStructからインスタンスを作成する
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);
// もっと賢く、型を自動的に推論
let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);
// きれいなマクロ!
// 🔴vec!マクロを使用することで
let string_vec = vec![String::from("Hello"), String::from("World")];
for word in string_vec.iter() {
println!("{}", word);
}
}
👇マクロとは
所有権
- 束縛するとメモリリソースが作成
- すべてのライフタイムに渡って Rust コンパイラが検証
- 束縛された変数はリソースの所有者
- デストラクトとの対比でストラクトと呼んでも良さそう
struct Foo {
x: i32,
}
fn main() {
// 構造体をインスタンス化し、変数に束縛してメモリリソースを作成
let foo = Foo { x: 42 };
// foo は所有者
}
スコープベースリソース管理
- デストラクトと解放のことをドロップ (drop)
- スコープから外れた瞬間、自動的にメモリが解放される
- Rust にはガベージコレクションがありません
struct Foo {
x: i32,
}
fn main() {
let foo_a = Foo { x: 42 };
let foo_b = Foo { x: 13 };
println!("{}", foo_a.x);
println!("{}", foo_b.x);
// foo_b はここでドロップ
// foo_a はここでドロップ
}
ドロップは親から階層的に行われる
- 構造体がドロップされると、まず構造体自体がドロップされ、次にその子要素が個別に削除
- 👇の例だと、Fooがドロップ→Bar
struct Bar {
x: i32,
}
struct Foo {
bar: Bar,
}
fn main() {
let foo = Foo { bar: Bar { x: 42 } };
println!("{}", foo.bar.x);
// foo が最初にドロップ
// 次に foo.bar がドロップ
}
所有権の移動
- 所有者が関数の実引数として渡されると、所有権は関数の仮引数に移動 (move)
- 移動している間、所有者の値のスタックメモリは、関数呼び出しパラメータのスタックメモリにコピーされます。
struct Foo {
x: i32,
}
fn do_something(f: Foo) {
println!("{}", f.x);
// f はここでドロップ
}
fn main() {
let foo = Foo { x: 42 };
// 🔴foo の所有権は do_something に移動
do_something(foo);
// foo は使えなくなる
}