codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
에듀›Tauri 2 — 데스크탑 · 모바일 한 코드베이스›3단계

3단계

IPC — command / event

0회 조회

IPC — command / event

프론트 (TypeScript) 와 백엔드 (Rust) 가 대화하는 두 가지 방식.

  • command (invoke) — 프론트 → Rust, 결과 반환. 함수 호출처럼.
  • event (emit / listen) — 양방향 · 비동기 알림. pub/sub 패턴.

1. command — Rust 측 정의

// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    tokio::fs::read_to_string(&path).await.map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet, read_file])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
  • #[tauri::command] 가 자동으로 JSON 직렬화
  • Result<T, String> 로 실패 전파

2. command — 프론트 호출

import { invoke } from "@tauri-apps/api/core";

const msg = await invoke<string>("greet", { name: "World" });
// "Hello, World!"

try {
  const content = await invoke<string>("read_file", { path: "/etc/hosts" });
} catch (e) {
  console.error(e);
}

파라미터 이름이 Rust 의 snake_case 와 일치해야 자동 매핑.

3. event — 양방향

use tauri::Emitter;

#[tauri::command]
async fn start_job(app: tauri::AppHandle) -> Result<(), String> {
    for i in 1..=100 {
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
        app.emit("job-progress", i).map_err(|e| e.to_string())?;
    }
    app.emit("job-done", ()).map_err(|e| e.to_string())?;
    Ok(())
}
import { listen } from "@tauri-apps/api/event";

const unlisten = await listen<number>("job-progress", (e) => {
    console.log("progress:", e.payload);
});
await invoke("start_job");

// 정리
unlisten();

listen 핸들러 정리를 잊으면 컴포넌트 언마운트 후에도 호출됨.

4. capabilities — 권한 화이트리스트

src-tauri/capabilities/default.json:

{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "dialog:allow-open",
    "fs:allow-read-text-file",
    "shell:allow-open"
  ]
}

기본 deny. 필요한 것만 opt-in. 파일 읽기 · shell 실행 같은 민감 기능은 명시.

5. 에러 전파 패턴

#[derive(Debug, thiserror::Error, serde::Serialize)]
enum Error {
    #[error("io error: {0}")]
    Io(String),
    #[error("not found")]
    NotFound,
}
// impl From<std::io::Error> for Error { ... }

#[tauri::command]
async fn safe_op() -> Result<String, Error> { ... }

프론트에서 catch (e) 로 타입 좁은 핸들링.

6. 자주 걸리는 자리

  • invoke_handler 등록 누락 — #[tauri::command] 만 써도 generate_handler! 에 추가해야
  • 비동기 Rust 함수 블로킹 — async + tokio::fs 같은 비동기 I/O
  • 이벤트 핸들러 중복 — useEffect 마다 listen 이 쌓임. cleanup 필수
  • JSON 직렬화 실패 — Rust 타입에 #[derive(serde::Serialize)] 누락

하고픈 말

command 는 REST 의 GET / POST 처럼, event 는 WebSocket / SSE 처럼 생각하면 설계가 편합니다. 90% 는 command 로 충분.

Next

  • 04-local-sqlite

← 2단계

프로젝트 셋업

4단계 →

로컬 SQLite