"다리 위에서 양쪽이 동의하는지 검사하는 컴파일러는 없어. 그러니 그 보장을 네가 직접 만들어 — 아니면 생성해."
정직한 틈
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의 자기 퀘스트야. 넌 다리를 건넜어. 이 퀘스트 나머지는 건너편에서 지어져.