Saltar al contenido

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

Rust
// src-tauri/src/main.rs — y no crece nunca#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]fn main() {    mi_app_lib::run()}
Rust
// 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

Rust
#![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

Rust
#[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:

Rust
// 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:

TOML
# 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.