State: el estado compartido en un comando
Registrar un valor al construir la aplicación y recibirlo en cualquier comando. Qué exige el tipo, por qué casi siempre necesita una envoltura para poder mutarse, y el error de tiempo de vida que produce olvidarlo.
Registrar y recibir
// lib.rs — al construir la aplicaciónpub struct Sesion { pub usuario: String,}tauri::Builder::default() .manage(Sesion { usuario: "willy".into() }) // se registra por su TIPO .invoke_handler(tauri::generate_handler![quien_soy])// y llega a cualquier comando que lo pida#[tauri::command]async fn quien_soy(sesion: tauri::State<'_, Sesion>) -> Result<String, ErrorPayload> { Ok(sesion.usuario.clone())}Tres cosas que conviene fijar de ese fragmento:
Se identifica por el tipo, no por un nombre. No hay una clave: registras un valor y lo pides declarando su tipo. La consecuencia es que solo puede haber un valor de cada tipo. Si necesitas dos cachés, no son dos HashMap: son dos tipos propios, y eso resulta ser mejor diseño de todas formas.
Pedir un tipo que no se registró compila y falla al ejecutar. El framework no puede saber en tiempo de compilación qué registró el builder, así que el error aparece al invocar el comando, con un mensaje que dice que el estado no está gestionado. Es un primo del olvido del nivel 14.2.
El guion bajo del tiempo de vida es obligatorio en un comando asíncrono. State<'_, Sesion>. Sin él, el compilador se queja de un tiempo de vida que no puede inferir, con un mensaje que no menciona ni Tauri ni comandos. Es de los errores que más despistan la primera vez.
Mutar: la envoltura
El estado se registra una vez y llega compartido a todos los comandos, que además pueden correr a la vez. Así que Rust no te va a dejar mutarlo sin más: hace falta una envoltura que garantice que dos comandos no lo tocan al mismo tiempo.
El caso de todos los días, con cerrojo síncrono:
use std::sync::Mutex;pub struct Contador(pub Mutex<u32>);// al construir.manage(Contador(Mutex::new(0)))#[tauri::command]async fn incrementar(c: tauri::State<'_, Contador>) -> Result<u32, ErrorPayload> { let mut n = c.0.lock().unwrap(); // el cerrojo se toma… *n += 1; Ok(*n)} // …y se suelta aquí, al acabar el bloqueEl error del tiempo de vida
Merece verlo escrito porque es el que más se busca en foros:
// Falta el '_#[tauri::command]async fn quien_soy(sesion: tauri::State<Sesion>) -> Result<String, ErrorPayload> { … }error[E0106]: missing lifetime specifier | | async fn quien_soy(sesion: tauri::State<Sesion>) … | ^ expected named lifetime parameterLa traducción: State es una referencia al valor gestionado, así que tiene un tiempo de vida, y en una función asíncrona el compilador necesita que se lo digas. '_ significa «el que corresponda, ya lo deduces tú». En un comando síncrono no hace falta, lo cual añade confusión: el mismo código compila o no según la palabra async.