Saltar al contenido

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
// 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
// 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
// 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.