Open33

🐣 Rust swing

sakusaku

流石に動かす
https://doc.rust-jp.rs/book-ja/ch00-00-introduction.html

  • 低レベルコードは微細なバグを抱える傾向があるが、Rustはバグのあるコードをコンパイルしない(静的解析を行う)
  • Cargoは、ビルドシステム兼パッケージマネジャ
    • コードのビルド、コードが依存するライブラリのダウンロード、それらのライブラリのビルドを行う
    • CargoはRustとともにインストールされる
    • npm init -yみたいにcargo new pj_nameでプロジェクト作成できる
  • RustfmtはRustのフォーマッタ
    • rustfmtはrustcなどと同様に標準的にrustに組み込まれている
  • rustupはRustのバージョンマネージャ

Rustを学ぶ過程で重要な部分は、コンパイラが表示するエラーメッセージを読む方法を学ぶことです

sakusaku

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を実行し、コンパイルできるか確かめるのに便利!
sakusaku

初めてのビルドでCargo.lockにexactバージョンが書き込まれる
ビルドが成功してCargo.lockが生成されたということは、現在の依存関係は解決されて正しいということ
手動で明示的にバージョンをアップデートプロジェクトはビルドの際に初回ビルドのCargo.lockを参照し続ける

sakusaku

cargo updateについて詳しい動き

0.8.40.9.0の二つがリリースされていたとする
→cargo updateを実行
0.8.4->メジャーバージョンのアップデートは自動で行わない!!

0.9.0にしたいときはCargo.tomlを書き換える必要

sakusaku

こんな感じでエラーハンドリングしていないことを教えてくれる

sakusaku

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のおかげですべてのコードに互換性があるようになる

sakusaku

cargo doc --openでプロジェクト内で使用されているクレートのドキュメントを開くことができる!!!すごい!!!

クレートのどのトレイトをuseするか、どのメソッドを呼び出すかを知るために使えそう

sakusaku

変数のシャドーイング

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);
}
sakusaku

  • ブール型 - bool は true または false を表します
  • 符号なし整数型 - u8 u32 u64 u128 は正の整数を表します
  • 符号付き整数型 - i8 i32 i64 i128 は正と負の整数を表します
    • 数値型は、数値の最後に型を付加することで明示的に指定できます。(例: 13u32, 2u8)
  • ポインタサイズ整数型 - usize isize はメモリ内のインデックスとサイズを表します
  • 浮動小数点数型 - f32 f64
  • タプル型 - (value, value, ...) スタック上の固定された値の組を渡すために使用されます
  • 配列型 - コンパイル時に長さが決まる同じ要素のコレクション
  • スライス型 - 実行時に長さが決まる同じ要素のコレクション
  • str(string slice) - 実行時に長さが決まるテキスト
sakusaku

同じ数値型でも型が正確に同じでなければならない

fn main() {
    let a = 13u8;
    let b = 7u32;
    let c = a as u32 + b; // 🔴このようにas で型をキャストする
    println!("{}", c);

    let t = true;
    println!("{}", t as u8);
}
sakusaku

定数

  • 定数は明示的な型指定が必要
  • 定数名には大文字のスネークケース SCREAMING_SNAKE_CASE
const PI: f32 = 3.14159;

fn main() {
    println!(
        "ゼロからアップル {} を作るには、まず宇宙を創造する必要があります。",
        PI
    );
}
sakusaku

配列

  • データ要素がすべて同じ型
  • 固定長
  • データ型は [T;N] であり、T は要素の型、N はコンパイル時に決まる固定長
  • [x]で要素を取得することができる(※x は取り出そうとする要素の usize 型のインデックス(0 始まり))
sakusaku

タプル

  • 複数の値を持てる
  • タプルの要素はインデックス番号で参照
  • ()版のオブジェクト{}的な?
  • 空のタプルは ()で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);
}
sakusaku

構文
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!");
        }
    }
}
sakusaku

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());
}
sakusaku

struct

  • interface的な?
  • 定義はメモリ上で隣合うデータの配置をコンパイラに伝える設計図の様なもの
struct SeaCreature {
    // String は構造体である。
    animal_type: String,
    name: String,
    arms: i32,
    legs: i32,
    weapon: String,
}
sakusaku

スタティックメソッド

  • スタティックメソッド - ある型そのものに紐付き、演算子 :: で呼び出せます。
  • インスタンスメソッド - ある型のインスタンスに紐付き、演算子 . で呼び出せます。
fn main() {
    // スタティックメソッドでStringインスタンスを作成する。
    let s = String::from("Hello world!");
    // インスタンスを使ってメソッド呼び出す。
    println!("{} is {} characters long.", s, s.len());
}
sakusaku

メモリ空間

  • データメモリ - 固定長もしくは スタティック (例: プログラムのライフサイクルで常に存在するもの)なデータ。 プログラム内の文字列(例: ‘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
    );
}
sakusaku

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"),
    }
}
sakusaku

ジェネリクス

  • 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
    );
}
sakusaku

::<T>で型を代入できるのだが、この書き方をturbofishという

sakusaku

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!("何も見付からなかった"),
    }
}
sakusaku

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(())
}
sakusaku

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(())
}

sakusaku

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);
    }
}
sakusaku

所有権

  • 束縛するとメモリリソースが作成
  • すべてのライフタイムに渡って Rust コンパイラが検証
  • 束縛された変数はリソースの所有者
  • デストラクトとの対比でストラクトと呼んでも良さそう
struct Foo {
    x: i32,
}

fn main() {
    // 構造体をインスタンス化し、変数に束縛してメモリリソースを作成
    let foo = Foo { x: 42 };
    // foo は所有者
}
sakusaku

スコープベースリソース管理

  • デストラクトと解放のことをドロップ (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 はここでドロップ
}
sakusaku

ドロップは親から階層的に行われる

  • 構造体がドロップされると、まず構造体自体がドロップされ、次にその子要素が個別に削除
  • 👇の例だと、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 がドロップ
}
sakusaku

所有権の移動

  • 所有者が関数の実引数として渡されると、所有権は関数の仮引数に移動 (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 は使えなくなる
}