El problema de las dos definiciones
Todo lo que cruza el puente está descrito dos veces, en dos lenguajes, y nada comprueba que coincidan. El coste real de esa duplicación, medido en las cuatro clases de fallo que produce.
Todo lo que está duplicado
// Rust: la verdad#[derive(Serialize)]#[serde(rename_all = "camelCase")]pub struct ProyectoResumen { pub id: u32, pub nombre: String, pub numero_ficheros: usize,}#[tauri::command]pub async fn cargar_proyecto(id: u32) -> Result<ProyectoResumen, ErrorPayload> { … }// TypeScript: la copia, escrita a mano y mantenida por la memoria de alguientype ProyectoResumen = { id: number; nombre: string; numeroFicheros: number;};const p: ProyectoResumen = await invoke('cargar_proyecto', { id });Lo que hay duplicado en ese fragmento pequeño:
- El nombre del comando, como cadena.
- Los nombres de los argumentos, con su conversión de convención.
- Los tipos de los argumentos.
- La forma completa del tipo de retorno, con la conversión de nombres de cada campo.
- La forma del error.
- Y, implícitamente, el hecho de que el comando exista.
Seis cosas, en un ejemplo de dos funciones. En una aplicación con treinta comandos y quince tipos, son cientos.
Las cuatro caras del mismo fallo
Por qué ninguna mitad puede detectarlo
Es la pregunta que explica por qué esto no se arregla con más cuidado:
Rust no puede ver el frontend. Compila su lado y no tiene forma de saber qué cadenas hay en un fichero de TypeScript, ni qué tipos alguien escribió a mano.
TypeScript no puede ver Rust. Comprueba que tu código use el tipo ProyectoResumen correctamente, y da por buena la afirmación de que eso es lo que devuelve el comando, porque se la hiciste tú con una anotación.
// TypeScript comprueba todo salvo lo único que importa: que esto sea verdad.const p = (await invoke('cargar_proyecto', { id })) as ProyectoResumen;Esa conversión de tipo es una promesa sin respaldo. El compilador la acepta y a partir de ahí razona sobre una forma que puede no existir.
El coste, en número
Merece la pena estimarlo para tu proyecto antes de decidir:
| lo que cuesta hoy | cómo medirlo |
|---|---|
| Mantener los tipos a mano | cuántos tipos cruzan × cuántas veces cambian al mes |
| Los fallos que llegan a ejecución | cuántas veces al mes alguien dice «no llega el argumento» |
| El fallo silencioso del campo | el peor: no se puede contar, porque no se detecta |
| La fricción de cada cambio | tocar dos ficheros en dos lenguajes para añadir un campo |
Y el coste de la solución, para comparar: una dependencia, un paso de generación, y un fichero generado en el repositorio. Los tres niveles siguientes son exactamente eso.