はじめに
RustでWindowsのネイティブアプリケーションはどこまで作れるのか。
Rustといえばシステムプログラミングやコマンドラインツールのイメージが強いですが、Windowsのデスクトップアプリを開発する言語としてはあまり話題になりません。しかしMicrosoft公式のwindows-rsクレートが出たことで、Win32 API・COM・WinRTにRustからアクセスできる環境は整っています。
実際にアプリケーションを一つ作り切ることで、どこまで実用的にやれるのか検証してみました。
RustとAIプログラミングの相性
今回の開発はClaudeでのAIプログラミングです。制作を中断したTauri製アプリケーションは主体的にコード生成も行っていましたが、今回はコーディングをAIに任せました。ただ一般的なバイブコーディングと違い、コーディング作業のレイヤー近くまで開発ユーザーが介入します。
さて、「RustはAIプログラミングと相性が良いのでは?」という命題の理由ですが、まず型システムが明示的です。pubキーワードによるアクセス制御がコードの構造にそのまま出るので、AIが「この関数は外部公開」「このフィールドはモジュール内限定」といった設計意図を正確に読み取れます。
動的型付け言語だと「この変数は何型か」「この関数はどこから呼ばれるか」で曖昧さが出がちですが、Rustではそれが構造的に少ない。
次に厳格なコンパイラの存在が大きいです。
AIが生成したコードに型の不整合やモジュール参照の誤りがあっても、コンパイラが具体的なエラーメッセージを返します。そのエラーをそのままAIに渡すだけで的確な修正が返ってくる。コンパイラが事実上「もう一人のレビュアー」として機能します。
モジュール構造が明確な点も効きます。ファイルの責務分離や構造変更を指示する際、「何がどこに属するか」が曖昧にならないので、リファクタリングの指示も具体的に出せますし、AIが返すコードも配置先を間違えにくいようです。
検証の題材
検証の題材として「Multi Mouse Tool」というWindowsデスクトップアプリを開発しました。
複数のマウスデバイスを接続した環境で、操作中のマウスを自動検知し、デバイスごとに異なる設定(カーソル速度・ポインター精度・ボタン左右入れ替え・スクロール速度)を即座に切り替えるツールです。
同種のツールとしてはEitherMouse(AutoHotkey製、2009年〜)がほぼ唯一の存在ですが、AHKのDllCall経由でのAPI呼び出しによる制約や、スリープ復帰時のデバイス見失う、といった構造的な問題がありました。
この題材を選んだのは、Rustの検証に必要な要素が揃っていたからです。
- Raw Input APIによるリアルタイムデバイス検知
- SystemParametersInfoWによるOS設定の読み書き
- GUIウィンドウとタスクトレイ常駐
- デバイス着脱のホットプラグ対応
- 設定のJSON永続化
実用的なWindowsアプリに必要な要素を一通りカバーしており、「Rustからどれだけ自然にWin32 APIを叩けるか」のベンチマークになります。
使用技術
windows-rsクレート(0.58)
Windows APIをRustから直接呼び出します。デバイスの列挙にGetRawInputDeviceList、操作中デバイスの検知にRegisterRawInputDevicesとWM_INPUTメッセージ、マウス設定の読み書きにSystemParametersInfoWを使用しています。
呼び出し部分はunsafeブロックが必要ですが、C言語で書くのと感覚は近いです。windows-rsが型安全なラッパーを提供しているので、Cよりは間違えにくい。
pub fn get_mouse_speed() -> i32 {
use windows::Win32::UI::WindowsAndMessaging::{
SystemParametersInfoW, SPI_GETMOUSESPEED, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
};
let mut speed: i32 = 0;
unsafe {
let _ = SystemParametersInfoW(
SPI_GETMOUSESPEED,
0,
Some(&mut speed as *mut i32 as *mut _),
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
);
}
speed
}
native-windows-gui(NWG)1.0.13
軽量なWin32 GUIラッパーです。ListView、TrackBar、CheckBox、タスクトレイ通知など標準的なコントロールを扱えます。
ただしNWGが標準イベントとして公開していないメッセージもありました。WM_INPUT(マウス入力)やWM_DEVICECHANGE(デバイス着脱)がそれです。これらはbind_raw_event_handlerで直接Win32メッセージを受信するようにしました。
nwg::bind_raw_event_handler(
&app.window.handle,
0x10001,
move |_hwnd, msg, _w, l| {
match msg {
WM_INPUT => {
// デバイスハンドルを取得して設定を切り替える
}
WM_DEVICECHANGE => {
// デバイス一覧を再構築
}
_ => {}
}
None
},
)
NWGの守備範囲内はNWGに任せ、それを超える部分はwindows-rsで直接叩く。この組み合わせで、フレームワークの制約に縛られず必要な機能を実現できました。
serde + serde_json
デバイスごとの設定をDeviceConfig構造体として定義し、JSONで永続化しています。serde(default)を使うことで、フィールドを追加しても既存の設定ファイルを壊さない後方互換性を持たせています。
#[derive(Serialize, Deserialize, Clone)]
pub struct DeviceConfig {
pub instance_id: String,
pub display_name: String,
pub speed: i32,
#[serde(default = "default_monitored")]
pub monitored: bool,
#[serde(default = "default_enhance_pointer_precision")]
pub enhance_pointer_precision: bool,
#[serde(default = "default_swap_buttons")]
pub swap_buttons: bool,
#[serde(default = "default_wheel_scroll_lines")]
pub wheel_scroll_lines: i32,
}
embed-resource
ComCtl32 v6有効化マニフェスト、アプリケーションアイコン、バージョン情報をEXEに埋め込んでいます。NWGはComCtl32 v6を前提としているため、マニフェストがないと実行時にパニックします。build.rs、resource.rc、.exe.manifestの3ファイル連携が必要で、RustでWindows開発をする際に避けて通れないポイントです。
開発プロセス
段階的な積み上げ
最小限の動作確認から順に進めました。
- NWGでウィンドウを表示してタスクトレイに格納
- Raw Input APIでマウスデバイスを列挙
- WM_INPUTで操作中デバイスをリアルタイム検知
- デバイス切り替え時にカーソル速度を自動適用
- 設定のJSON永続化
- UI整備(ポインター精度、ボタン入れ替え、スクロール速度)
- リファクタリング
各段階で動作確認してから次に進むので、問題が起きても原因の切り分けがしやすいです。
AIとのワークフロー
私的AIバイブコーディングに則り、実装作業サイクルは「設計相談→実装指示書の作成→Claude Codeでの実行→テスト→修正」です。
設計段階ではデータ構造やイベントフローを相談し、方針が固まったら実装指示書をMarkdownで作成してClaude Codeに渡します。AIが直接コードを自由に書き換えるのではなく、指示書を介することで意図しない変更を防いでいます。
ただ通常のバイブコーディングと違い、修正できないロジックバグが出た場合や、AIが場当たり的なバグ修正に走った場合は、セッションをリファクタリングしたり、コーディング層近くまで開発者が手を加えます。
これはメタ認知力がない(そもそもメタ認知そのものの概念がない)AIを補助する目的です。
コンパイラが活きた場面
Win32 APIのScreenToClient関数をWindowsAndMessagingモジュールからインポートしようとしてコンパイルエラーが出たことがあります。そのエラーメッセージをそのままAIに渡したところ、正しいモジュール(Graphics::Gdi)を特定して修正を返してきました。
NWGのTrackBarでRange<{integer}>とOption<Range<usize>>の型不一致が出た際も、コンパイラの提案を含むエラーがそのまま修正の手がかりになりました。
Rustのエラーメッセージは人間にとっても親切ですが、AIにとっても解釈しやすい構造化された情報になっています。
バグの根本原因分析
デバイス切り替え時にスクロール設定が0にリセットされるバグが出たことがあります。AIに実行フローを追わせたところ、プログラムによるUI更新(set_posやset_check_state)がイベントハンドラを意図せず発火させ、不正な値をconfigに書き戻していたことがわかりました。
修正はフラグ1つ(is_updating_ui)の追加だけで済みましたが、問題の特定にはコード全体の実行パスの理解が必要でした。こうした構造的な分析はAIの得意分野です。
リファクタリング
680行のapp.rsをmod.rs(ロジック)・ui.rs(UI構築)・events.rs(イベントハンドラ)の3ファイルに分割しました。レイアウト定数もlayout.rsに分離しています。
機能を一切変えずにコード品質だけを上げる作業は、コンパイラが変更前後の整合性を保証してくれるので、AIに任せやすい領域でした。
まとめ
RustでWindowsネイティブアプリは普通に作れます。
Win32 APIの呼び出し部分にはunsafeが必要ですが、アプリ全体に占める割合はわずかで、それ以外の大部分ではRustの所有権システムやエラーハンドリングの恩恵を受けられます。
GUIフレームワークの選択肢はC#と比べると限られます。NWGはメンテナンスが活発とは言えず、複雑なUIには向きません。ただしユーティリティアプリやシステムツールのようなシンプルなUIであれば十分実用的で、フレームワークが足りない部分はwindows-rsで直接補完できます。
RustとAIプログラミングの相性は想像以上と自分は感じました。明示的な型システムがAIの理解精度を上げ、コンパイラがAIの出力品質を担保し、明確なモジュール構造がAIへの指示を具体的にする。AIとの協業でソフトウェアを開発する際に、Rustは有力な選択肢になると思います。
GitHub: https://github.com/ya-ma-n-1972/multi-mouse-tool
補足: 「C#の方がいいのでは?」という話
記事を書いた後、思い付いた疑問を補足しておきます。
C#のWPF/WinFormsの方が生産性が高いのでは
一般論としてはそうです。WPFならXAMLでデータバインディングが宣言的に書ける。WinFormsならVisual Studioのデザイナーでドラッグ&ドロップでUIが組める。.NETのクラスライブラリは厚く、情報量も桁違いに多い。
ただしこの優位は「複雑なGUIアプリ」の話です。今回のMulti Mouse ToolのUIはListView、TrackBar、CheckBox、トレイアイコン程度で、WPFのデータバインディングもWinFormsのデザイナーも必要になっていません。NWGで十分間に合っています。
さらに、このアプリの要はGUIではなくRaw Input APIやSystemParametersInfoの直接呼び出しです。C#でもP/InvokeやDllImportで同じようにunsafeな領域に踏み込む必要があり、C#の生産性の優位が効きにくい部分です。
高機能化したらTauriに移行すべきか
おそらく不要。EitherMouse並の機能を目指す場合でも、追加される機能(ミラーカーソル、スクロール方向反転、ダブルクリック速度、Raw Accel連携など)はすべてバックエンド側の処理です。UIとしてはCheckBoxやSliderが数個増える程度で、NWGの守備範囲内です。
むしろTauriに移行するデメリットの方が大きい。今のアプリはNWGのbind_raw_event_handlerで直接Windowsメッセージループに入り込んでWM_INPUTをリアルタイム受信しています。TauriのWebView2ベースのウィンドウで同じことをやるには独自のメッセージフックを組む必要があり、今動いている安定した仕組みを捨てることになります。WebView2ランタイムへの依存が増え、バイナリサイズも数百KBから数十MBに跳ね上がる。常駐型ユーティリティとしてはマイナスです。
じゃあ結局C#で作った方がよかったのでは
これはC#のコード資産を持っているかどうかで答えが変わると思います。
C#の生産性の優位は、.NETエコシステムに慣れていること、既存コードの再利用、Visual Studioのデザイナー活用、この3つが揃って初めて発揮されます。ゼロから学ぶなら、その優位はほぼ消えます。しかもWin32 API直叩きが核心のアプリでは、C#を選んでも一番重要な部分でC#らしさが活きません。
私の場合、C#のコード資産は全くなく、Rustのコード資産を今まさに積み上げている最中です。Win32 APIのラッパー(device.rs、mouse_config.rs、config.rs)が実働するコードとして手元にある。AIとの開発フローもRustで確立されている。ここから言語を変えれば、コードもワークフローも作り直しです。
ただ、このままAIプログラミングが発展するようでしたらコード資産という概念自体が変わるかもしれません。Anthropic社がCOBOLで構築された大規模レガシーシステムの改修をAIで成功させたとされています。言語やフレームワークの壁をAIが超えるようになれば、特定言語のコード資産に縛られる必要はなくなるでしょう。
あと、「小規模ユーティリティにはC#が最適」というのはC#経験者にとっての一般論であって、万人に当てはまる結論ではありません。