Saltar al contenido

El registro, y por qué se olvida

El paso que separa una función que existe de un comando que se puede invocar. Por qué el compilador no puede avisarte, las tres formas de que deje de olvidarse, y el error exacto que produce.

El registro

Rust
tauri::Builder::default()    .invoke_handler(tauri::generate_handler![        resumir_carpeta,        cargar_proyecto,        guardar_proyecto,    ])    .run(tauri::generate_context!())

Ese macro construye, en tiempo de compilación, la tabla que el tramo 5 del recorrido consulta: un nombre de comando en texto, una envoltura que lo atiende. Un comando que no esté en la lista no está en la tabla, y el tramo 5 no lo encuentra.

Por qué el compilador no avisa

Es la pregunta buena, y la respuesta explica el diseño entero.

Una función con el atributo de comando, sin registrar, es una función de Rust perfectamente válida que nadie llama. El compilador puede avisar de que una función privada no se usa, y ni siquiera eso si es pública, porque una función pública sin usar es lo normal en una librería. No hay nada anómalo que detectar.

Al revés tampoco: generate_handler! recibe nombres de funciones, y si escribes uno que no existe, eso sí falla al compilar. La asimetría es esa:

TXT
comando registrado que no existe   →  error de compilacióncomando que existe y no se registra →  silencio, y falla al ejecutar

Y encima, el fallo aparece en el otro lenguaje, en otro proceso, cuando alguien pulsa el botón que lo usa. Por eso cuesta.

El error, y cómo se distingue

TXT
Unhandled Promise Rejection: Command resumir_carpeta not found

Lo que importa es distinguirlo del error de permisos, porque los dos ocurren antes de que tu comando corra y se confunden:

Las tres formas de que deje de pasar

1 · La disciplina. Escribir el comando y registrarlo en el mismo commit, siempre. Funciona mientras alguien se acuerde, que es hasta que el equipo crece.

2 · Una prueba de humo que toque todos los comandos. Es la del nivel 13.3: un recorrido de extremo a extremo que invoque cada comando una vez. No hace falta comprobar el resultado; basta con que no diga «no encontrado». Caza el olvido en integración continua en vez de en la máquina de un usuario.

3 · Generar los bindings. Es la solución de fondo y el nivel 19: si los tipos de TypeScript se generan desde los comandos registrados, un comando sin registrar no produce función que llamar, y el frontend deja de compilar. El olvido pasa de fallo en ejecución a error del editor.

TypeScript
// Con bindings generados, esto no compila si el comando no está registrado.import { commands } from './bindings';const r = await commands.resumirCarpeta('/tmp');

Merece la pena decirlo claro: la tercera hace innecesarias las otras dos, y por eso el nivel 19 existe. Las dos primeras son lo que se hace mientras tanto.

Y el error simétrico

Existe el contrario y es menos conocido: registrar un comando dos veces. El macro no se queja, y el que gana es impredecible. Ocurre cuando dos módulos exportan una función con el mismo nombre y las dos entran en la lista, cosa fácil con nombres genéricos como cargar o guardar.

Es una razón más para lo que el nivel 14.4 va a pedir: nombres que digan la operación de dominio y no el mecanismo. Con cargar_proyecto y cargar_ajustes la colisión no existe.