콘텐츠로 이동
Tauri

상태 관리

Tauri 애플리케이션에서는 종종 애플리케이션의 현재 상태를 추적하거나 애플리케이션과 관련된 다양한 것들의 라이프사이클을 관리해야 합니다. Tauri에서는 Manager API를 사용하여 애플리케이션의 상태를 관리하고, 명령이 호출되었을 때의 상태를 읽기 위한 간단한 방법을 제공합니다.

다음은 간단한 예입니다:

use tauri::{Builder, Manager};
struct AppData {
welcome_message: &'static str,
}
fn main() {
Builder::default()
.setup(|app| {
app.manage(AppData {
welcome_message: "Welcome to Tauri!",
});
Ok(())
})
.run(tauri::generate_context!())
.unwrap();
}

Manager 트레이트를 구현하는 모든 유형(예: App 인스턴스)은 나중에 해당 상태에 액세스할 수 있습니다:

let data = app.state::<AppData>();

각 명령의 상태 액세스를 포함하여 자세한 내용은 상태 액세스 섹션을 참조하십시오.

Rust에서는 여러 스레드 간에 공유되는 값이나 소유권이 Arc(또는 Tauri의 State)와 같은 공유 포인터를 통해 제어되는 값을 직접 변경할 수 없습니다. 그렇게 하면 (예: 두 개의 쓰기가 동시에 발생하는 등) 데이터 경합이 발생할 수 있기 때문입니다.

이를 피하기 위해 내부 가변성이라는 개념을 사용합니다. 예를 들어, 표준 라이브러리의 Mutex는 “상태를 래핑”하는 데 사용할 수 있습니다. 이를 통해 값을 변경해야 할 때 잠그고 변경이 끝나면 잠금을 해제합니다.

《번역 주》 Mutex “Mutex”는 “mutual exclusion”(상호 배제)의 약자로, 어떤 경우에도 하나의 스레드에만 데이터에 대한 액세스를 허용하는 메커니즘입니다. “Mutex 뮤텍스”에 대한 자세한 설명은 Rust Book 16.3을 참조하십시오.

use std::sync::Mutex;
use tauri::{Builder, Manager};
#[derive(Default)]
struct AppState {
counter: u32,
}
fn main() {
Builder::default()
.setup(|app| {
app.manage(Mutex::new(AppState::default()));
Ok(())
})
.run(tauri::generate_context!())
.unwrap();
}

“뮤텍스”를 잠그면 상태를 변경할 수 있습니다:

let state = app.state::<Mutex<AppState>>();
// 뮤텍스를 잠그고 가변 액세스를 얻습니다:
let mut state = state.lock().unwrap();
// 상태를 변경합니다:
state.counter += 1;

범위가 끝나거나 MutexGuard가 삭제되면 “뮤텍스”는 자동으로 잠금 해제되어 애플리케이션의 다른 부분이 내부 데이터에 액세스하고 해당 데이터를 변경할 수 있게 됩니다.

《번역 주》 Tokio “Tokio”는 Rust용 비동기 런타임 라이브러리입니다.

Tokio의 Mutex 문서에서 인용하면, 다음과 같이 Tokio가 제공하는 것과 같은 비동기 뮤텍스 대신 표준 라이브러리의 Mutex를 사용해도 괜찮은 경우가 많습니다:

일반적으로 믿어지는 것과는 반대로, 비동기 코드에서 표준 라이브러리의 일반 Mutex를 사용해도 문제없으며, 종종 그것이 선호됩니다. … 비동기 뮤텍스의 주요 사용 사례는 데이터베이스 연결과 같은 IO 리소스에 대한 공유 가변 액세스를 제공하는 것입니다.

이 두 Mutex 간의 트레이드오프(일장일단)를 이해하려면 각각의 위 링크된 문서를 충분히 읽어보는 것이 좋습니다. 비동기 뮤텍스가 필요하게 될 수도 있는 이유 중 하나는 “대기 지점”을 넘어 MutexGuard를 유지해야 하는 경우입니다.

《번역 주》 Arc구조체 Arc”(Struct Arc). Atomically Reference Counted(원자적 참조 카운트)의 약자.

Rust에서는 여러 스레드 간에 값의 소유권을 공유하기 위해 Arc가 일반적으로 사용됩니다(보통 Arc<Mutex<T>> 형태로 Mutex와 함께 사용됩니다). 그러나 State에 저장된 것에 대해서는 Arc를 사용할 필요가 없습니다. 왜냐하면 Tauri가 대신 이를 실행하기 때문입니다.

State의 수명 요구 사항으로 인해 “상태”를 새 스레드로 이동할 수 없는 경우, 대신 AppHandle을 해당 새 스레드로 이동하여 아래 “Manager 트레이트를 사용하여 상태에 액세스하기” 항목에서 보여주는 것처럼 “상태”를 가져올 수 있습니다. AppHandle은 이러한 유스케이스(사용 사례)에서 의도적으로 복제가 저렴합니다.

《번역 주》 의도적으로 복제가 저렴 원문 deliberately cheap to clone. (문맥 불명: AppHandle에서 유사한 처리를 간편하게 수행할 수 있도록 한다는 의미?)

#[tauri::command]
fn increase_counter(state: State<'_, Mutex<AppState>>) -> u32 {
let mut state = state.lock().unwrap();
state.counter += 1;
state.counter
}

명령에 대한 자세한 내용은 “프론트엔드에서 Rust 호출하기” 장을 참조하십시오.

async 명령을 사용하고 Tokio의 비동기 Mutex를 사용하려면 다음과 같이 유사한 방법으로 설정하면 “상태”에 액세스할 수 있습니다.

#[tauri::command]
async fn increase_counter(state: State<'_, Mutex<AppState>>) -> Result<u32, ()> {
let mut state = state.lock().await;
state.counter += 1;
Ok(state.counter)
}

비동기 명령을 사용하는 경우 반환 유형은 Result여야 한다는 점에 유의하십시오.

경우에 따라 다른 스레드 내나 on_window_event와 같은 이벤트 핸들러 내와 같이 명령 외부에서 “상태”에 액세스해야 할 수 있습니다. 이러한 경우 Manager 트레이트를 구현하는 유형(AppHandle 등)의 state() 메서드를 사용하여 상태를 가져올 수 있습니다:

use std::sync::Mutex;
use tauri::{Builder, Window, WindowEvent, Manager};
#[derive(Default)]
struct AppState {
counter: u32,
}
// 이벤트 핸들러 내:
fn on_window_event(window: &Window, _event: &WindowEvent) {
// 전역 상태를 가져올 수 있도록 앱에 대한 핸들을 가져옵니다.
let app_handle = window.app_handle();
let state = app_handle.state::<Mutex<AppState>>();
// 뮤텍스를 잠그고 "상태"에 가변적으로 액세스합니다.
let mut state = state.lock().unwrap();
state.counter += 1;
}
fn main() {
Builder::default()
.setup(|app| {
app.manage(Mutex::new(AppState::default()));
Ok(())
})
.on_window_event(on_window_event)
.run(tauri::generate_context!())
.unwrap();
}

이 방법은 명령 주입(외부에서의 명령 실행)에 의존할 수 없는 경우에 유용합니다. 예를 들어, AppHandle을 사용하는 것이 더 간단한 스레드에 “상태”를 이동해야 하는 경우나 명령 컨텍스트 내에 존재하지 않는 경우 등입니다.

《번역 주》 명령 주입에 의존할 수 없음 원문은 cannot rely on command injection. (문맥 불명) 《번역 주》 명령 컨텍스트 원문 a command context. 명령 실행에 필요한 정보나 환경.

필요한 경우 이 실수를 방지하기 위해 “상태”를 “유형 별칭”으로 래핑할 수도 있습니다:

use std::sync::Mutex;
#[derive(Default)]
struct AppStateInner {
counter: u32,
}
type AppState = Mutex<AppStateInner>;

단, 유형 별칭은 그대로 사용하고 Mutex로 다시 래핑하지 않도록 하십시오. 그렇지 않으면 동일한 문제를 일으킬 수 있습니다.

【※ 이 한국어판은, 「Mar 28, 2025 영문판」에 근거하고 있습니다】


© 2025 Tauri Contributors. CC-BY / MIT