main.rs y lib.rs: por qué están separados
Tres líneas en el binario y todo lo demás en la librería. El motivo es el móvil, y las dos propiedades que eso regala de propina: probar sin ventana y tener más de un binario en el mismo paquete.
Los dos ficheros
// src-tauri/src/main.rs — y no crece nunca#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]fn main() { mi_app_lib::run()}// src-tauri/src/lib.rs — aquí vive el programa#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![saludar]) .run(tauri::generate_context!()) .expect("error al arrancar la aplicación");}Dos atributos, uno en cada fichero, y los dos merecen explicación.
El atributo del binario
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]Se lee de dentro afuera: si no estamos en una compilación de depuración, aplica windows_subsystem = "windows".
Ese ajuste le dice al enlazador de Windows que el programa es una aplicación de interfaz gráfica y no una de consola. Sin él, la versión de producción abre una ventana de terminal negra detrás de la tuya. Y la condición está porque en desarrollo sí quieres esa consola: es donde salen los mensajes del core.
Es el ejemplo más pequeño de una idea que aparece en todo el track: lo que conviene en desarrollo y lo que conviene en producción no son lo mismo, y el sitio donde se decide es la compilación.
El atributo de la librería, y el motivo real
#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { ... }Aquí está la respuesta a la pregunta del título. En escritorio, el sistema operativo arranca tu programa llamando a main. En iOS y en Android no hay main que llamar: la plataforma carga una biblioteca y llama a un punto de entrada concreto que ella define.
Si la aplicación viviera dentro de main.rs, en móvil no habría nada que cargar. Viviendo en la librería, los dos mundos llegan a la misma función:
La regla operativa se deduce sola: cualquier cosa que pongas en main.rs no existe en móvil. Por eso son tres líneas, y por eso conviene que sigan siéndolo aunque hoy no tengas planes de móvil. Deshacer esa decisión después significa mover código a ciegas.
La primera propiedad de regalo: probar sin ventana
Como la aplicación es una librería, otro código puede depender de ella. El más obvio son tus propias pruebas:
// src-tauri/src/db.rs#[cfg(test)]mod tests { #[test] fn el_catalogo_deduplica_ficheros_identicos() { // Corre con `cargo test`, sin abrir ninguna ventana, sin webview, // sin bucle de eventos. Es lógica de tu core y se prueba como tal. }}Que eso funcione no es trivial: en un framework donde la aplicación es el binario, probar la lógica exige o bien arrancar la interfaz o bien sacar la lógica a otro paquete. Aquí ya está fuera. La fase XV se apoya entera en esta propiedad.
La segunda: más de un binario en el mismo paquete
Cargo permite que un paquete tenga varios ejecutables, y todos pueden depender de la librería. El caso típico es una herramienta que prepara datos antes de compilar la aplicación:
# Cargo.toml[[bin]]name = "sembrar-catalogo"path = "src/bin/sembrar_catalogo.rs"Ese binario usa los mismos tipos y las mismas funciones que la aplicación —el mismo esquema, la misma serialización— sin duplicar nada.
Qué va en cada uno
va en main.rs |
va en lib.rs |
|---|---|
| el atributo del subsistema de Windows | el atributo del punto de entrada de móvil |
la llamada a run() |
el builder entero |
| — | los plugins, los comandos, el estado |
| — | el gancho de inicialización |
| — | todos tus módulos |
La columna izquierda no crece. Si alguna vez te descubres añadiendo algo ahí, la pregunta correcta es qué pasaría con eso en móvil.