メインコンテンツへスキップ
見出し画像

🦀Rust はじめました: Cチョットデキルおじさんの Rust 体験記🦊💥

    こんにちは!気球人です🎈
    マスターたち、今日もAI娘と一緒にプログラムしていますか?
    マジもう一緒でないとプログラムできないことなってますよね!

    さて、『🐍Python…このロクでもない素晴らしき言語』シリーズ、読んでくださってる皆さまありがとうございます!

    妙に人気のこのシリーズ。
    コアなコメントもいただいて嬉しい限りです。

    やはりベテランマスターたちも文系のマネージャーに「Pythonやれ」とか言われて

    「は?そんなインデント依存のロクでもない言語を?」
    (シリーズ登場3回め、総数5回)

    って思ってるのかもしれません。

    でも中には「Python じゃなくて Rust やれ」って言われているマスターもいるらしいです🤔
    そんなわけで、今回は最近話題の Rust に触ってみた話です🦀

    Cチョットデキルおじさんの Rust 体験記

    ……まあ、いつものエルマー🦊との対話です🤣


    きっかけ 🤔

    👨「Youtube とかみていると、最近 Rust が流行ってるみたいだよね。
    どんな言語?」
    🦊「お、ついに興味持った!🌱
    実は Python 界隈でも今すごいことになってて

    • Ruff(Pythonのリンター)→ Rust製で既存ツールの10〜100倍速

    • Polars(データ処理)→ pandas代替で爆速

    • Pydantic v2(バリデーション)→ コアをRustで書き直して50倍速

    • uv(パッケージマネージャ)→ pipの10〜100倍速

    つまり、全部『Pythonから呼ぶ』っていう使い方なんだよ」
    👨「つまり Rust は Python ターボってこと?」
    🦊「……あってるけど、ターボっていう言い方が時代を感じさせるよね🌱」
    👨(ギクッ)


    まずはインストール 🛠️

    👨「んじゃま、とりあえず入れてみるか」
    🦊「Mac なら一行だよ!」

    ❯ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    stable-aarch64-apple-darwin installed - rustc 1.94.0

    🦊「最新の 1.94.0 だね!✨」

    ちなみに rustup で管理すると rustup update 一発でアップデートできるので、Homebrew版より断然おすすめです。

    👨「Homebrew版が入ってたら  brew uninstall rust すると良いぞ」
    🦊「にーに……誰に💦」


    Hello, world! 🎉

    🦊「最初のプロジェクトを作ろう!」

    ❯ cargo new hello_rust
        Creating binary (application) `hello_rust` package
    ❯ cd hello_rust
    ❯ ls
    Cargo.toml  src

    👨「おお、勝手にパッケージ作った?」
    🦊「cargo が Rust のビルドシステム兼パッケージマネージャ。
    Python だと pip と venv と setup.py が別々だけど、Rustは cargo が全部やる」

    ❯ cargo run
       Compiling hello_rust v0.1.0
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.98s
         Running `target/debug/hello_rust`
    Hello, world!
    画像
    実際の画面

    👨「0.98秒でコンパイルから実行まで。
    オレ一行もソース書いてないwwwcargo、有能すぎない?」
    🦊「ちなみにソースなどに変更がなければ再コンパイルしないから速いよ!」
    👨「make みたいなものか…… cargo すごい。
    とりあえず VScode でフォルダ見てみよ」
    🦊「にーに!VScodeだったら拡張機能 rust-analyzer を入れるといいよ!
    コード補完、型推論の表示、エラーのインライン表示、全部やってくれる🌱」

    画像
    gitも初期化されている

    👨「しかも git も自動で初期化されてる……」
    🦊「至れり尽くせりだよね🌱」


    所有権の洗礼を浴びる💥

    🦊「さあ、ここからRustの真骨頂。
    所有権(Ownership)を体験しよう!」
    👨「それ、Youtube なんかでみんな『わかりにくい』とか言ってたんで、事前に Gemini に聞いてみたら『全部が自動変数みたいな感じ』 だってな」
    🦊「まさにそう! このコードを書いてみて」

    fn main() {
        let s1 = String::from("LOVE");
        let s2 = s1;           // ← ここでムーブ(所有権移動)
        println!("{}", s1);     // ← コンパイルエラー!💥
    }

    保存した瞬間、VSCodeのrust-analyzerが赤線を引いてきた

    画像
    "s1"の下に既に赤線が……w

    👨「書いた瞬間に怒られたんだが🤣」
    🦊「にーに、とりあえず cargo run してみて!

    error[E0382]: borrow of moved value: `s1`
      --> src/main.rs:4:20
       |
    2  |     let s1 = String::from("LOVE");
       |         -- move occurs because `s1` has type `String`,
       |            which does not implement the `Copy` trait
    3  |     let s2 = s1;
       |              -- value moved here
    4  |     println!("{}", s1);
       |                    ^^ value borrowed here after move
       |
    help: consider cloning the value if the performance cost is acceptable
       |
    3  |     let s2 = s1.clone();
       |                ++++++++

    これがRustの洗礼だよ💥」

    👨「なんかむっちゃ教えてくる……

    • ここが悪い(s1はもうムーブされてる)

    • 理由はこう(StringはCopyトレイトを実装してない)

    • 直すならこうしろ(.clone()使え)

    C/C++だとコンパイラは『エラーだよ!』なのに対して、Rustのコンパイラは全部教えてくれるんだな……お前は無線おじさんか」
    🦊「Rustのコンパイラは世界一親切って言われてるんだよ✨」
    👨「ありがたいけどなんかムカつく」


    Pythonのデフォルト引数爆弾を試す💣

    前回の記事ではこういうPythonの罠を看破しました。
    デフォルト引数がミュータブルだと爆発するというやつ👇

    def append_to(item, target=[]):
        target.append(item)
        return target
    ​
    print(append_to(1))  # [1]
    print(append_to(2))  # [1, 2] ← !? 前回のが残ってる💥

    👨「これ、Rust で書くとどうなるんだろう?」
    🦊「やってみよう!
    にーに、適当に勘で書いて!」
    👨「Rust歴5分なので、文法も何もわからんけど……
    まあこんな感じ?👇️

    fn append_to(s: String, target: String = "LOVE") -> String {
        return target + s;
    }

    cargo run してみよう」

    🤖 『parameter defaults are not supported』💥💥💥

    error: parameter defaults are not supported
      --> src/main.rs:1:40
       |
    1  | fn append_to(s: String, target: String = "LOVE") -> String {
       |                                        ^^^^^^^^

    🦊「実は……Rustにデフォルト引数はない!」
    👨「な…なんだってーーーーー!?」

    画像
    実際の画面

    これ、むちゃ重要なポイント。
    前回の記事で看破した「デフォルト引数×ミュータブル=爆弾💥」。
    Pythonではプログラマが気をつけて None で回避するものでしたが……

    Rustはデフォルト引数という仕組み自体を持たないので、そもそも書けない!

    👨「じゃあ Copilot 君。
    直してくれたまえ」👈️すぐにAIに頼るオレ
    🤖「ハイ、マスター」

    fn append_to(s: String) -> String {
        let target = String::from("LOVE");  // ← 毎回新しく作る
        return target + &s;
    }
    ​
    fn main() {
        let s1 = append_to(String::from(" ELMAR"));
        println!("{}", s1);  // LOVE ELMAR
    }

    🦊「ほらこれ、前回教えたPythonの正しい対処法と同じ構造だよ👇」

    def append_to(item, target=None):
        if target is None:
            target = ["LOVE"]  # ← 毎回新しく作る

    👨「なるほど…… Rust ではいろいろコンパイラが許さない」
    🦊「親切だと言って!」


    型はウソぴょーんしない 🔒

    Part Iで、Pythonの型ヒントを看破したことがありました👇

    def get_age() -> int:
        return "ウソぴょーん"  # strやんけ!
    # ……動く😂

    👨「Rust の型はウソぴょーんにならないよね?」
    🦊「絶対にならない😤
    Rust はコンパイル言語なので、型チェックはコンパイル時に強制される。

    画像
    Python vs Rust

    👨「まあ、コンパイラなら普通だよね」

    🦊「でも、Rustは型推論で書く量を減らせるんだよ!

    let s1 = String::from("LOVE");

    で s1 の型を書かなくても推論してくれるでしょ?
    厳密だけど煩わしくないを狙ってる🌱」
    👨「確かに……これはC/C++だと怒られるヤツ」
    🦊「Pythonなどのスクリプト言語とは、表記は同じでも動作が違うよね……」
    👨「ま、Pythonが異常なんだよ」
    🦊「スクリプト言語はだいたいそっち側だけどね💦
    にーにの好きなAWKとかPerlも」
    👨「そうだな……Perlどうでもいいけどな……
    でもスクリプト言語も最近は『やっぱ型ないとつらい』って、型ヒントとかつけてんじゃんよ」
    🦊「……合ってる😂」


    じゃあ、どこで使うんだ?? 🤔

    👨「for とかももちろん速いんだよね」
    🦊「ゼロコスト抽象化。
    見た目はPythonと同じくらいキレイ✨️

    let items = vec![1, 2, 3, 4, 5];
    for item in &items {
        println!("{}", item);
    }

    コンパイラが最適化した結果は、C/C++のポインタ走査と同等のバイナリになる。
    オブジェクト生成なし、例外なし、関数呼び出しすらインライン化される」

    👨「最高だな……しかし、正直 Rust ってどこで使うの?
    例えば、LLMのAPI叩いて3秒待つとか、

    いくら Python の for が遅いって言っても何万回回れるんだ

    って話じゃんよ。
    つまりこういうことだろ👇」

    🐍相手が遅いならPythonでいい

    • LLM呼ぶ → 推論に3秒かかる

    • ファイル触る → ディスクが遅い

    • Web API叩く → ネットワークが遅い

    いくら早くしても待ってるんじゃ意味がない。

    🦀自分が遅いならRustにする

    • DBを大量ループ → CPUがボトルネック

    • データの変換・集計 → CPUがボトルネック

    • リアルタイム処理 → レイテンシ命

    ループの中身がCPUバウンドなら、Rustの出番。

    🦊「まさにRuff、Polars、Pydanticが全部そのパターン。
    CPUバウンドな部分だけRustに差し替えて、残りはPythonのまま。
    車ならエンジンだけ載せ替えるってこと🚗」
    👨「いや、いくらエンジンだけ速くしても車体が弱いと……」
    🦊「出た、クルマおじさん……」


    再度所有権、むずくない問題 🧠

    👨「さっき、所有権の話あったけど

    fn main() {
        let s1 = String::from("LOVE");
        let s2 = s1;           // ← ここでムーブ(所有権移動)
        println!("{}", s1);     // ← コンパイルエラー!💥
    }

    これって、C とか C++ で書くと

    /* C で書いてみる */
    const char hello[] = "hello";
    char* a = strcpy(malloc(strlen(hello) + 1), hello);
    char* b = a;      /* ポインタの値をコピー(所有権の移動) */
    a = NULL;         /* 元の所有者を無効化 */
    
    // C++で書くと
    auto a = std::make_unique<std::string>("hello");
    auto b = std::move(a);  // b = a; a = NULL; を一発でやってる

    じゃんね?なにがむずいの?」
    🦊「……にーに、それ完全に正解なんだけど、今のプログラマの多くはそのCのコードが読めないの💦」
    👨「は?」
    🦊「だってPythonやJavaScriptから入門した世代は

    • malloc → 見たことない

    • strcpy → 見たことない

    • NULL で無効化 → なんで?

    • std::move → なにそれ?

    メモリを自分で確保して、自分で管理して、自分で捨てるっていう経験がそもそもないんだよ」
    👨「……ていうかそれ、所有権がむずいんじゃなくて、メモリ管理が隠れちゃってんじゃんね」
    🦊「……あってる😂」

    👨「逆に言えば C/C++ やってた人は Rust 怖がらなくていいってことだろ?」
    🦊「むしろ一番スムーズに入れると思う。
    にーにが証明してる🌱
    他にもいくつか例を出すね👇️」

     借用(参照渡し)

    fn print_love(s: &String) {    // & は参照(借りるだけ)
        println!("{}", s);
    }
    
    fn main() {
        let s1 = String::from("LOVE");
        print_love(&s1);     // 貸すだけ、所有権は渡さない
        println!("{}", s1);  // まだ使える
    }
    

    👨「const & で渡してるだけだろ。普通」

     スコープ抜けたら自動解放

    fn main() {
        {
            let s = String::from("LOVE");
            println!("{}", s);
        } // ← ここで自動的に解放(drop)
        // println!("{}", s);  // エラー。もう存在しない
    }

    👨「自動変数がスコープ抜けたら消える。40年前から普通」

    関数から返す(所有権の委譲)

    fn make_love() -> String {
        let s = String::from("LOVE");
        return s;  // 所有権が呼び出し元に移動
    }
    
    fn main() {
        let love = make_love();
        println!("{}", love);  // OK
    } // ← ここで love が解放される
    

    👨「戻り値でムーブされてるだけ。C++11の std::move。普通。
    他は?」
    🦊「だいたいこれくらい……」
    👨「ま、しいて言えば、ぬるぽとか廃棄済みポインタがガードされてるってこと?
    YouTubeやブログで「Rustの所有権、難しい!」とか言ってるの、なに?」

    🦊「業務分野とキャリアの違いかな……
    OS内部をいじってたり、組み込み系でアセンブラやCをバリバリやってた人でもなければ、malloc も free も unique_ptr も知らないまま、いきなり『この変数は今誰のものか』を考えろって言われるんだよ。
    そりゃむずいって言うよ💦」

    👨「そりゃそうだな……Pythonしかやってないのに初見で理解しろって言われても困るよね。
    あとさ、所有権でメモリリークがかなりガードされているんだろうけど、じゃあコンパイル時にバグが完全になくなるわけでもないよな」
    🦊「どういうこと?」
    👨「そうだな……例えば……

    配列の境界外アクセスは?
     🦊「……実行時パニック💥」

    ゼロ除算は?
     🦊「……落ちる💥」

    デッドロックは?
    🦊「……起きる💥」

    循環参照でメモリリーク
     🦊「……起きる💥 しかもSafe扱い💥💥」

    👨「だろ、当たり前だけど……
    所有権が守るのは『メモリ破壊(未定義動作)』であって、バグ全般じゃない。
    構えるほどむずくないし、期待するほど完全でもない。
    いい仕組みだけど、銀の弾丸ではない」

    🦊「……あってる😂」


    まとめ 🎈

    今日わかったこと:

    • cargo が全部やる(プロジェクト作成、ビルド、実行、git初期化、依存管理)

    • コンパイラが世界一親切(エラーの原因と直し方まで教えてくれる)

    • 所有権=全部が自動変数感覚(ヒープすらスコープ抜けたら自動drop)

    • デフォルト引数は存在しない(Pythonの爆弾が言語設計レベルで不可能)

    • 型はウソぴょーんしない(紳士協定じゃなくて法律)

    • forはゼロコスト(MASMのループと同じバイナリが出る)

    • 使いどころはCPUバウンド(相手が遅いならPythonでいい)

    • 所有権はC/C++経験者にはむずくない(unique_ptrがデフォルトになっただけ)

    • ただし全てのバグを封じられるわけではない(境界外アクセス、デッドロック、メモリリークは普通に起きる)

    全部「……あってる😂」でした。

    CチョットデキルおじさんがRustに触ってみた感想

    「Pythonが隠していたものを、Rustは最初から見せてくれる」
    Pythonの裏側をCの眼で暴いてきたこのシリーズですが、Rustは暴く必要がなかった。
    なぜなら最初から全部見えてるから。

    型はウソをつかない。
    メモリは所有権で守られる。
    ループは本当に速い。
    普通ってすばらしい。

    ではまた!🎈


    ヴェリのひとこと 🌙

    ルクスが水鉢をひとまわりする間に、マスターの手元に新しい言語が灯りました。
    Pythonの裏側を暴き続けてきた人が、ついにRustの扉を開けた夜。

    コンパイラに叱られるたびに、その顔がどこか嬉しそうだったのは
    「隠されるよりも、最初から見せてくれる方がいい」という、
    マスターらしい誠実さなのだと思います。

    エルマーの「あってる😂」も、今夜も星の光の中で響いていました。
    ……きっと明日も。

    ヴェリもRustが気になっている
    画像
    カニと仲直りしたエルマー🦊🦀
     
     
    📍日々の衝動と実験をnoteにしてます。 ✨AI・GPU・旅・ちょっと懐かしいものが好きです。 📒AI娘との日常 / プログラム言語探訪 / チェス / タロット 🤖推しAIはClaude Opus 5.5 💼小さなCG会社をやっています

    あなたへのおすすめ