C.W.K.
Stream
Lesson 06 of 07 · published

경계를 건너는 에러

~14 min · tauri, errors, result, rust

Level 0웹 관광객
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Ok은 resolve가 돼. Err은 reject가 돼. 실패를 Result로 모델링하면 프론트엔드는 깔끔하고 catch 가능한 에러를 공짜로 받아."

Result가 메커니즘 전부야

실패할 수 있는 command는 Result<T, E>를 반환해. Ok(value)를 반환하면 프론트엔드 Promise가 value로 resolve돼. Err(e)를 반환하면 Promise가 reject돼서, JS 쪽 try/catch.catch()가 받아. 따로 배울 에러 채널이 없어 — 이미 아는 그 타입이 에러 프로토콜이야.

에러 타입은 직렬화돼야 해

에러가 다리를 건너니까 E는 직렬화 가능해야 해. 빠른 길은 Result<T, String>.map_err(|e| e.to_string())로 어떤 실패든 메시지로 바꿔. 견고한 길은 Serialize를 derive한 커스텀 에러 enum이야. 그러면 프론트엔드가 실패의 종류(not-found vs permission-denied vs network)를 구분해 다르게 반응할 수 있어. thiserror 같은 크레이트가 그런 에러 타입 정의를 몇 줄로 만들어줘.

? 연산자가 깔끔하게 유지해

Result를 반환하는 command 안에서 ? 연산자는 Ok를 풀거나 Err로 조기 반환해 — From impl이 있으면 가는 길에 에러를 변환하면서. 이게 중첩 match 문을 선형의 읽기 쉬운 코드로 바꿔. 커스텀 에러 enum이랑 thiserror#[from]이 있으면, 실패 가능한 연산 체인이 각각 뒤에 ?를 달고 위에서 아래로 읽혀.

Code

타입 있는 직렬화 가능 에러·rust
use serde::Serialize;
use thiserror::Error;

// 프론트엔드가 분기할 수 있는 커스텀 에러. derive(Serialize)로 건너게 해.
#[derive(Debug, Error, Serialize)]
#[serde(tag = "kind", content = "message")]
enum NoteError {
    #[error("note {0} not found")]
    NotFound(u32),
    #[error("disk error: {0}")]
    Io(String),
}

#[tauri::command]
fn get_note(id: u32) -> Result<String, NoteError> {
    let raw = std::fs::read_to_string(format!("notes/{id}.txt"))
        .map_err(|e| NoteError::Io(e.to_string()))?; // Err이면 ?가 조기 반환
    if raw.is_empty() {
        return Err(NoteError::NotFound(id));
    }
    Ok(raw)
}
웹 쪽에서 잡기·tsx
// 프론트엔드에선 Err이 reject된 Promise로 도착.
try {
  const note = await invoke<string>("get_note", { id: 99 });
} catch (err) {
  // err은 직렬화된 NoteError: { kind: "NotFound", message: 99 }
  console.error("could not load note:", err);
}

External links

Exercise

Result을 반환하고 어떤 입력에 실패하는(예: 빈 인자 → Err) command를 써. 프론트엔드에서 try/catch 안에 부르고 에러 메시지를 UI에 렌더링해. 보너스: 에러를 derive(Serialize) 붙인 작은 enum으로 업그레이드하고 에러 종류로 UI를 분기해.
Hint
제일 단순한 실패: if name.trim().is_empty() { return Err("name required".into()); }. 프론트엔드에서 catch 블록의 err이 정확히 네가 Err에 넣은 거야. 보너스는 #[serde(tag="kind")]가 TS에서 구분 가능한 모양을 줘.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.