Rustなんもわからん部〜チュートリアルを読むPart1/n〜

# Rustなんもわからん

Rustなんも分かりません。わからないのでおててを動かしていきます。
今週は超省エネ記事です。

# Rustって何？

gpt 4oに聞いてみました！

> Rustとは、高速性・安全性・並行性を兼ね備えたプログラミング言語です。Mozillaが中心となって開発を始め、現在は独立したRust Foundationによってメンテナンスされています。
> 
> 🔧 Rustの主な特徴
> 1. メモリ安全性
> Rustの最大の特徴は、「ガーベジコレクタなしでメモリ安全性を保証できる」点です。
> C/C++のような低レベル言語ではポインタの扱いが原因でバグやセキュリティホールが生じやすいですが、Rustはコンパイル時に所有権（Ownership）という仕組みで不正なメモリアクセスを防ぎます。
> 
> 2. 高速な実行性能
> RustはCやC++と同じくネイティブコンパイルを行うため、実行速度が非常に速く、ハードウェアに近いレベルの制御も可能です。
> 
> 3. 並行性（並列処理）が安全に扱える
> Rustはデータ競合（Data Race）をコンパイル時に防止できるため、スレッドや非同期処理を安全に実装できます。
> async/awaitによる非同期プログラミングもサポートされています。

だそうです！なんか強そう！
実際、dbt fusionがRustで書かれたことで、非常に高速化しているそうです。！

https://zenn.dev/suwash/articles/dbt_fusion_engine_20250530

すごそう！なので、今回はRustのチュートリアル(bookなるもの)を見ていきます。
英語版とその日本語訳版がありますが、今回は日本語訳版を見ていきたいと思います。

https://doc.rust-jp.rs/book-ja/

他の参考資料は以下の通りです。


https://www.rust-lang.org/ja/learn/get-started

https://doc.rust-jp.rs/rust-by-example-ja/


# チュートリアルを読む会

チュートリアルをひたすら読んで、内容をまとめていきます。
今回は2章までです。

## 1.事始め

Rustは、コンパイル型(コンパイラ型？)言語の一種なので、Pythonのようなインタプリタ言語とは異なり、一度コードをコンパイルして機械語に直す必要があります。
その際、以下のようにコードファイルをコンパイルすることが出来ます。
```rust
// main定義
fn main() {
    // 世界よ、こんにちは
    println!("Hello, world!"); 
    // マクロを読み出しているので!マークがついている
}
```
```shell
$ rustc main.rs # コンパイル
$ ./main # コンパイルしたファイルを実行
Hello, world!
```

しかしながら、このように一つ一つのコードをいちいちコンパイルしていたらきりがないので、`cargo`というビルドシステム兼パッケージマネージャーが存在します。これを使うことで、コードのビルド、依存ライブラリのダウンロードなどをまとめて行うことが出来ます。

```shell
$ cargo new hello_cargo # プロジェクトの作成
$ cd hello_cargo

# ビルド
$ cargo build
   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs

# コンパイルしたファイルの実行
$ ./target/debug/hello_cargo
Hello, world!

# ビルドと実行を両方実行
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/hello_cargo`
Hello, world!
```

このように、`cargo`を使うとかなり便利にコンパイルから実行まで行うことが出来ます。以下のような有用なコマンドが記載されていました。
```shell
cargo new # 新規プロジェクトの作成
cargo build # コードのコンパイル
cargo run # コードのコンパイルと実行
cargo check # バイナリを生成せずにビルドしてエラーがないか確認できる
cargo build --release
# ビルド成果物をtarget/releaseに作成する
# 最終成果物などはこちらの方法でビルドすることを想定されている
```

### まとめ

- Rustはコンパイル型言語
- cargoを使ってのビルド、実行が推奨

## 2.数当てゲームのプログラミング

ここでは、ランダム生成された整数を、ユーザーが当てるというゲームのプログラミングをしています。
初めに、コードの全体像を見てから、個別要素を解説するようにしていきます。

<details><summary>数当てゲームコード全体像</summary>

```rust
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    // 1~100の範囲で整数生成
    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
```
</details>

### useについて

コードの一番最初に、`use`という部分があります。これは、pythonでいうところのライブラリのimportのようなものだと思っています。
use rand::Rng;
use std::cmp::Ordering;
use std::io;
今回使っているのは、ランダムに関連する`rand::Rng`、列挙型であるenumの一種である`std::cmp::Ordering`、入出力に関連する`std::io`、です。
列挙型であるenumについては、とりうる値として決まった数の列挙子を持ち、match(ifみたいなやつ)と併用することで、条件式として活用できるそうです。
今回であれば、
```rust
match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
```
という部分がありますが、ユーザーの入力(guess)と、secret_number(当てる数)の大小比較をし、その結果に応じて別の文字列を出力するようになっています。
enum型については、6章でやるらしいです。

## letを用いた変数の宣言について

コードの中では、ユーザーからの入力を以下のように受け取っています。
```rust
// 可変な変数guessを宣言(mutをつけることでmutableに)
// string型の空文字列をはじめに入れている
let mut guess = String::new();

// ユーザーの入力を受け取る
io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");

// 入力が数字かどうかを判定し、数字の場合のみguessに代入
// ここでのguessは、不変な32bit符号なし整数として宣言
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};
```
大事なのは、変数の宣言時にすでに可変か不変かが決定されているというところです。これを入れていないと、例えば以下のようなコードだとエラーになってしまいます。
```rust
let apple = 5;
apple = apple + 1;

> error[E0384]: cannot assign twice to immutable variable `apple`
```
これは、不変で定義した変数を変更しようとしたために発生しています。なので、宣言する際に十分に注意しましょう。

余談ですが、以下のようにすればエラー自体は回避できます。
```rust
fn main() {
    let apple = 5;
    let apple = apple + 1;
}
```
エラー自体は出ませんが、変数自体を後から無理やり変えているようなものなので、多分やらない方がいいのかなと思います。

### ユーザーの入力を受け取る部分

ユーザーの入力を受け取っているのはこの部分になります。
```rust
io::stdin()
    .read_line(&mut guess)
```
`.read_line(&mut guess)`では、ユーザーからの入力を受け取って、可変的な参照としてguessに渡しています。(参照は&で行えますが、参照自体もデフォルトでは不変だそうです。)

### println!での変数表示

println!はマクロですが、そこで変数を表示するには`{}`を使う必要があります。
```rust
fn main() {
    let x = 5;
    let y = 10;
    
    println!("x = {0} and y = {1}", x, y);
    // 出力する文字列、代入する変数を順番で入れる感じ
    // {}内で数字を入れれば、何番目の変数を表示するかを制御できる
    // > x = 5 and y = 10

    // 表示順を変更するとこんな感じ
    println!("x = {1} and y = {0}", x, y);
    // > x = 10 and y = 5
}
```
また、これだけではなく、変数を表示する際にいろいろといじることもできます。

詳しくは、以下を参照してください。

https://doc.rust-jp.rs/rust-by-example-ja/hello/print.html

### loop

loopは、while trueのように、常にループし続けるようなものになっています。ですので、条件を満たしたとき(入力が答えと一致したとき)にbreakでloopが終了するようになっています。

### match
pythonのif文、sqlのcase文のような、条件分岐に使う式だという認識です。
```rust
match guess.cmp(&secret_number) {
    Ordering::Less => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal => {
        println!("You win!");
        break;
    }
}
```
今回であれば、`std::cmp::Ordering`を使い、その出力結果に応じて分岐するようになっています。

変数名.cmp(&比べる変数名)で、大小関係をとってこれるようです。

公式サンプルコードは以下の通り。
```rust
use std::cmp::Ordering;
// 1と2の大小比較
assert_eq!(1.cmp(&2), Ordering::Less);
// 1と1の大小比較
assert_eq!(1.cmp(&1), Ordering::Equal);
// 2と1の大小比較
assert_eq!(2.cmp(&1), Ordering::Greater);
```

https://doc.rust-lang.org/std/cmp/enum.Ordering.html


# おててを使って遊ぶ(はんずおん！)

実際に、手元で一度hello worldを実行してみたいと思います。

## Rust環境のコンテナを作る

Rust環境のコンテナを作ります。CopilotのAgent機能使って作ってもらいました。

<details><summary>コンテナ関連コード</summary>

```Dockerfile
# Rust開発環境用のDockerfile
FROM rust:latest

# 作業ディレクトリを設定
WORKDIR /workspace

# 必要なツールをインストール
RUN apt-get update && apt-get install -y \
    git \
    vim \
    nano \
    curl \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Rustを最新の安定版にアップデート
RUN rustup update stable

# Rustのコンポーネントを追加
RUN rustup component add rustfmt clippy

# 便利なcargoツールをインストール
RUN cargo install cargo-watch cargo-tree

# デフォルトのシェルをbashに設定
SHELL ["/bin/bash", "-c"]

# コンテナ起動時のデフォルトコマンド
CMD ["bash"]
```

```yaml:docker-compose.yml
version: '3.8'

services:
  rust-dev:
    build: .
    container_name: rust-study-container
    volumes:
      # dataフォルダをバインドマウント
      - ./data:/workspace/data
      # Cargoのキャッシュを永続化（ビルド時間短縮のため）
      - cargo-cache:/usr/local/cargo/registry
      - cargo-target:/workspace/target
    working_dir: /workspace
    stdin_open: true
    tty: true
    # ポートフォワーディング（Webアプリ開発時に便利）
    ports:
      - "8080:8080"
      - "3000:3000"
    environment:
      - RUST_BACKTRACE=1

volumes:
  cargo-cache:
  cargo-target:
```

</details>

## hello worldをする

まずはhello worldしていきます。前述の通り、rustc経由でやるか、cargo経由でやるかのパターンがあるので、どっちも試していきます。

### rustcを使ってのhello world

このサイトの内容をなぞっていきます。

https://doc.rust-jp.rs/rust-by-example-ja/hello.html

まずは、コードが以下のようになります。

```rust:hello_world.rs
// This is a comment, and is ignored by the compiler.
// You can test this code by clicking the "Run" button over there ->
// or if you prefer to use your keyboard, you can use the "Ctrl + Enter"
// shortcut.
// これはコメントです。コンパイラによって無視されます。
// 右にある「Run」ボタンからこのコードをテストできます。
// キーボードを使いたければ「Ctrl + Enter」もOKです。

// This code is editable, feel free to hack it!
// You can always return to the original code by clicking the "Reset" button ->
// このコードは編集可能です。ぜひハックしてみましょう！
// 「Reset」ボタンでいつでも元のコードに戻すことができます ->

// This is the main function.
// main関数です
fn main() {
    // Statements here are executed when the compiled binary is called.
    // コンパイルされたバイナリが実行されるとこの関数が呼び出されます

    // Print text to the console.
    // コンソールに文字列を出力する
    println!("Hello World!");
}
```
これによってできたファイルを、コンパイルしてバイナリファイルにして実行することで実行できます。

```
// コンパイル
rustc hello.rs

// ファイル実行
# ./hello-world/helloworld 
Hello World!
```

### cargoを使ってのhello world

このサイトの内容をなぞっていきます。

https://www.rust-lang.org/ja/learn/get-started

```
# 新しいプロジェクト(hello-rust)の作成
cargo new hello-rust
```

これによって以下のフォルダが作られます。

```plaintext
hello-rust
|- Cargo.toml # プロジェクトのメタデータや依存関係の記録
|- src
  |- main.rs
```

実際に、main.rsを見てみるとこのようになっています。

```rust:main.rs
fn main() {
    println!("Hello, world!");
}
```

つまり、実行するとターミナルに`"Hello, world!`を出力してくれるわけです。
実際に実行する場合は、以下のように、作成されたディレクトリに入って以下のようにして実行できます。

```
cd hello-rust # ディレクトリに移動
cargo run
>     Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
>      Running `target/debug/hello-world`
> Hello, world!
```

# まとめ

今回は、(全然時間を掛けられなかったので、)チュートリアルを少し読んで、Hello worldを走らせるというところまで行いました。
今後は、チュートリアルを一通り回ってから、pythonで使えるライブラリを作るところまで行けたらなと思います。