본문 바로가기
C.W.K.
Stream
Lesson 07 of 07 · published

끝에서 끝까지 타입 있는 IPC

~14 min · tauri, typescript, ipc, architecture

Level 0웹 관광객
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"다리 위에서 양쪽 말이 맞는지 검사해주는 컴파일러는 없어. 그러니 그 보장은 네가 직접 만들거나, 아예 기계한테 만들게 시켜."

아무도 안 메워주는 틈

invoke<Note>('get_note', ...)라고 쓸 때 Note는 확인된 사실이 아니라 네 주장이야. Rust랑 TypeScript는 따로 컴파일되거든. Rust command가 정말로 네 TS 인터페이스랑 같은 모양을 돌려주는지 검사하는 건 아무것도 없어. Rust에서 필드 이름 하나를 바꿔도 네 TS는 런타임에 깨질 때까지 거짓말 위에서 멀쩡히 컴파일돼. 잘 만든 Tauri 앱은 이 틈을 작정하고 메워.

전략 1: 타입 붙은 API 모듈 하나

최소한의 규율은 이거야. 컴포넌트에선 invoke를 생으로 절대 안 불러. 대신 command를 전부 타입 함수로 감싼 모듈 하나를 둬 — getNote(id: number): Promise<Note> 같은 식으로. 그럼 command 이름이랑 인자 모양이 정확히 한 곳에만 살아. command가 바뀌면 파일 하나만 고치고, 나머지 부르는 곳은 TypeScript가 다 짚어줘. fetch 호출을 API 레이어로 감싸던 그 본능을 IPC에 그대로 옮긴 거야.

전략 2: 타입을 생성해

더 튼튼한 선택지는 Rust command에서 TypeScript 바인딩을 뽑아내서 양쪽이 어긋날 수 없게 만드는 거야. tauri-specta 같은 도구가 네 command 시그니처를 읽고 타입이 붙은 클라이언트 함수를 내놔 — Rust에서 필드 이름을 바꾸면 생성된 TS가 따라 바뀌면서 런타임이 아니라 컴파일 타임에 빌드를 깨뜨려. 진지한 앱이면 세팅할 값이 있어. 아무도 안 메워주던 틈을 컴파일러가 지키는 계약으로 바꿔놓거든.

Rust는 여기서 어디로

여기까지가 다리야. 딱 필요한 만큼의 Rust, command, serde, async, 에러, 타입. 진짜 앱을 만들기엔 충분해. 언어를 더 깊이 파고 싶어질 때 — trait, lifetime, 제네릭, iterator 도구상자, 겁 없는 동시성 — 그건 /cwk-quests/rust-quest에 있는 자기 퀘스트야. 넌 다리를 건넜어. 이 퀘스트의 나머지는 건너편에서 지어져.

Code

전략 1: 타입 붙은 IPC 레이어를 모듈 하나에·ts
// src/lib/ipc.ts — invoke가 살아도 되는 유일한 곳.
import { invoke } from "@tauri-apps/api/core";

export interface Note {
  id: number;
  title: string;
  createdAt: string;
}

// 모든 command가 타입 래퍼를 받아. 컴포넌트는 이걸 import하지 invoke를 안 해.
export const getNote = (id: number) =>
  invoke<Note>("get_note", { id });

export const saveNote = (draft: Note) =>
  invoke<Note>("save_note", { draft });

// 이제 컴포넌트는: const note = await getNote(42);
// command 이름 바꿔? 여기서 한 번 고치면 TS가 모든 호출처를 잡아줘.

External links

Exercise

프론트엔드를 손봐서 어떤 컴포넌트도 invoke를 직접 안 부르게 만들어. src/lib/ipc.ts를 만들고, 모든 invoke를 거기 타입 래퍼 함수로 옮기고, 컴포넌트가 그걸 import하게 고쳐. IPC를 안전하게 바꿀 수 있는 이음매를 방금 지은 거야 — Cinder가 작정하고 둔 그 한 수.
Hint
command마다 함수 하나씩 export하고, 각자 invoke<ReturnType>(name, args)를 돌려주게 해. 다 옮겼으면 컴포넌트에서 invoke( 를 grep해봐 — ipc.ts 밖엔 0건이어야 해.

Progress

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

댓글 0

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

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