組み込み系エンジニアのえいとです。普段はC言語をメインで使っているのですが、最近「Rust」という言語がどうしても気になってきて、ついに手を動かしてみることにしました😊

Rustは「安全で高速なシステムプログラミング言語」として、ここ数年で急速に注目を集めています。今回はC言語歴の長い私の目線で、Rustを触ってみた率直な感想をお伝えします!

なぜ今Rustが注目されているのか

スポンサーリンク

Rustが特に注目を集めたきっかけのひとつが、AndroidカーネルへのRust採用です。Googleは2021年頃からAndroid OSのカーネル開発にRustを導入し始めており、従来のC/C++によるメモリ安全性の問題を解消しようとしています。

さらに大きなニュースとして、MicrosoftがWindowsカーネルの一部をRustへ移行する取り組みを進めています。長年C/C++で書かれてきたWindowsの中核部分にRustが採用されるというのは、業界全体への大きなシグナルだと感じました。Microsoftは「新しいコードの70%以上をRustで書く」という方針も示しており、本気度が伝わってきます。

Stack Overflowの開発者調査では、Rustは何年も連続して「最も愛されているプログラミング言語」に選ばれており、エンジニアからの支持がとても高い言語です。組み込み系エンジニアとしても、もはや無視できない存在になってきていると感じています。

C言語エンジニアが驚いたRustの特徴

Rustで最もユニークな概念が所有権(Ownership)・借用(Borrowing)・ライフタイム(Lifetime)の3つです。C言語使いからするとかなり独特で、最初はここで戸惑いました😅

所有権(Ownership)

Rustでは、変数はメモリ上のデータに対して「所有権」を持ちます。所有権は同時に1つの変数しか持てず、別の変数に代入すると元の変数は使えなくなります。これによって二重解放(double free)メモリリークといったバグをコンパイル時に防いでくれます。C言語でmallocしたポインタを管理する苦労を思うと、非常に魅力的な仕組みです。

借用(Borrowing)

所有権を渡さずにデータを使いたい場合は「借用」を使います。参照(&T)を使えば読み取り専用で、可変参照(&mut T)を使えば書き込みも可能です。ただし可変参照は同時に1つしか存在できないというルールがあります。C言語のポインタに似ていますが、より厳格なルールでデータ競合を防ぐ仕組みになっています。

ライフタイム(Lifetime)

参照が有効な期間をコンパイラが追跡する仕組みです。C言語でよくある「解放済みメモリへのアクセス(ダングリングポインタ)」をコンパイル時に検出できます。最初は構文が難解に見えますが、慣れると「コンパイラが守ってくれる」という安心感があります。

C言語とRustの文法比較

実際にコードを見比べてみましょう。変数宣言と関数定義から確認してみます。

C言語の場合:

int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 10;
    int y = 20;
    printf("result: %d\n", add(x, y));
    return 0;
}

Rustの場合:

fn add(a: i32, b: i32) -> i32 {
    a + b  // 末尾セミコロンなしが戻り値
}

fn main() {
    let x: i32 = 10;
    let y: i32 = 20;
    println!("result: {}", add(x, y));
}

大きな違いとして、Rustはデフォルトで変数が不変(immutable)です。値を変更したい場合はlet mutと宣言する必要があります。C言語のconstがデフォルトになったようなイメージで、最初は少し戸惑いましたが、バグを防ぐ意味では合理的だと感じました。

C言語とRustの主な違いをまとめると以下のようになります。

項目 C言語 Rust
変数宣言 int x = 10; let x: i32 = 10;
変数の可変性 デフォルトで可変 デフォルトで不変(mutが必要)
メモリ管理 malloc/free(手動) 所有権システム(自動・安全)
NULL参照 ポインタがNULLになりうる Option型で明示的に扱う
エラー処理 戻り値やerrno Result型で統一

実際に簡単なプログラムを書いてみた

まずは公式のインストールツールrustupでRustをセットアップしました。インストール自体は1コマンドで完了し、とても簡単でした。最初に書いたのはカウントダウンプログラムです。

fn main() {
    for i in (1..=10).rev() {
        println!("{}秒前...", i);
    }
    println!("発射!");
}

C言語だとfor (int i = 10; i >= 1; i--)と書くところが、Rustではイテレータの.rev()で逆順にできます。cargo run一発でコンパイル&実行できるのも気持ちよかったです😊

次にベクタ(動的配列)を使ったコードを試したとき、所有権で初めてつまずきました。let v2 = v1;とするとv1が使えなくなるというのは、C言語ではあり得ない挙動で最初は「バグ?」と思いましたが、これが安全性の根拠なのだと理解してからは納得感がありました。コンパイラのエラーメッセージがとても丁寧で、どこが問題でどう直せばよいかをきちんと教えてくれるのも助かりましたでした。

Rustのツールチェーンが便利すぎた

Rustを触って感動したのがツールチェーンの充実度です。C言語の開発では別途CMakeやMakefileを整備する手間がありますが、RustはCargoというパッケージマネージャ&ビルドツールがデフォルトで使えます。

  • cargo new プロジェクト名:プロジェクト作成
  • cargo build:コンパイル
  • cargo run:ビルド&実行
  • cargo test:テスト実行
  • cargo clippy:Lintチェック

さらにrustfmtという公式フォーマッタもあり、コードスタイルを自動で統一できます。依存ライブラリの追加もCargo.tomlに1行書くだけで済むので、C言語の開発環境構築と比べると圧倒的に楽でした。組み込み開発でこれだけのツールが標準で揃っているのは正直うらやましいと感じましたでした😊

C言語との使い分けを考える

一通り触ってみて、Rustはとても魅力的な言語だと感じましたが、C言語との使い分けは現実的には必要だと思っています。

マイコンの組み込み開発や、既存のCライブラリとの密な連携が必要な場面では、まだC言語の方がエコシステムが充実しています。一方でRustもno_std環境(標準ライブラリなし)に対応しており、組み込みRustの事例は少しずつ増えてきています。

新しいシステムソフトウェアや長期保守が必要なツールを作る場合は、Rustの「コンパイラが守ってくれる安全性」は大きな武器になると感じます。特にメモリ管理のバグに起因するセキュリティ脆弱性を防げる点は、C言語との最大の差別化ポイントでしょう。

当面は「C言語がメイン、Rustをサブで学ぶ」スタンスで進めていこうと思っています。まだ所有権の概念に慣れていない部分も多いですが、コンパイラのエラーメッセージが親切なので、独学でも学びやすい言語だと感じています。引き続き使ってみてまたレポートしますね😊

それでは、今回はここまで。ありがとうございました😊