前のレシピ (rust-006) では、State を使ってデータを共有しましたが、それは「読み取り専用」でした。
アプリの実行中にデータを「書き換える」(例:カウンターを増やす、リストに追加する)には、Mutex (Mutual Exclusion: 排他制御) という仕組みが必要です。
Rust では、複数の場所(スレッド)から同時にデータを書き換えるとバグの原因になるため、コンパイラが厳しくチェックします。
Mutex を使うと、「今このデータを触っているのは自分だけ」という状態(ロック)を作ることができ、安全に書き換えができるようになります。
1. 同期コマンドの場合 (std::sync::Mutex)
通常の(async ではない)コマンドで使う場合は、標準ライブラリの std::sync::Mutex を使います。
状態の定義 (State)
構造体のフィールドを Mutex<T> (Tはデータの型) で包みます。
use std::sync::Mutex;
struct CounterState {
// i32型のデータを Mutex で保護する
count: Mutex<i32>,
}
初期化して登録 (lib.rs)
src-tauri/src/lib.rs 内で初期化します。
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
// 初期化:Mutex::new(初期値) で包む
.manage(CounterState { count: Mutex::new(0) })
.invoke_handler(tauri::generate_handler![increment])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
コマンドの実装
#[tauri::command]
fn increment(state: tauri::State<'_, CounterState>) -> i32 {
// 1. .lock() でロックを取得します。
// もし他の誰かがロックしていたら、解放されるまでここで待ちます。
// 2. .unwrap() はロック取得に失敗した場合(通常は起きない)にパニックさせるために記述します。
let mut num = state.count.lock().unwrap();
// num は現在「可変参照」のような状態です。
// *num とすることで中身の値(i32)にアクセスできます。
*num += 1;
println!("Count: {}", *num);
// 現在の値を返します。コピーされるので安全です。
*num
// 関数エラーが終わると、`num` 変数がスコープから外れ、自動的にロックが解除されます。
}
2. 非同期コマンドの場合 (tokio::sync::Mutex)
async コマンドの中でロックを使う場合は、必ず tokio::sync::Mutex を使ってください。
標準の std::sync::Mutex をロックしたまま await (待機) すると、デッドロック(処理が永久に止まる)などの深刻な問題を引き起こす可能性があります。
状態の定義と初期化
use tokio::sync::Mutex; // ライブラリが違います!
struct AsyncState {
data: Mutex<Vec<String>>,
}
// 初期化は同様に .manage(AsyncState { data: Mutex::new(vec![]) })
コマンドの実装
#[tauri::command]
async fn add_item(state: tauri::State<'_, AsyncState>, item: String) -> Result<(), String> {
// 非同期ロックなので .lock().await を使います
// .unwrap() は不要です(Tokioのロックは poisoned 状態がないため)
let mut data = state.data.lock().await;
// 重い処理のシミュレーション
// ロック(data)を持ったまま await しても、TokioのMutexなら安全です
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
// データを変更
data.push(item);
println!("現在のリスト: {:?}", *data);
Ok(()) // 自動的にロック解除
}
まとめ
- データを変更したいなら:
Mutexで包む。 - 通常のコマンドなら:
std::sync::Mutexを使い、.lock().unwrap()で開く。 - async コマンドなら:
tokio::sync::Mutexを使い、.lock().awaitで開く。