16. Interfaces de escritorio en Rust, y luego Tauri
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
Ejemplo completo: l16-tauri/, un explorador de acordes de escritorio. Las lecciones 16 a 21 lo construyen; ejecuta npm install y luego npm run tauri dev desde esa carpeta.
Las lecciones 1 a 15 cubrieron el lenguaje. Esta segunda parte responde a una pregunta que todo desarrollador de C# o Java se hace tarde o temprano: ¿puedo escribir una aplicación de escritorio en Rust, y con qué?
Lo que ya conoces
Sección titulada «Lo que ya conoces»En .NET y en la JVM, se elige entre toolkits que dibujan sus propios controles y toolkits que integran un motor de navegador:
| Toolkit | Interfaz descrita en | Renderizado |
|---|---|---|
| WinForms | código C#, diseñador | controles Win32, solo Windows |
| WPF | XAML + bindings | su propio renderizador DirectX, solo Windows |
| .NET MAUI | XAML + bindings | los controles nativos de la plataforma (WinUI en Windows) |
| Avalonia | .axaml, parecido a XAML |
su propio renderizador, en todos los sistemas |
| Swing, JavaFX | código Java, FXML | su propio renderizador, en todos los sistemas |
| Electron | HTML, CSS, JavaScript | un Chromium incluido en cada aplicación, más Node.js |
| Blazor Hybrid | componentes Razor | el webview del sistema (WebView2 en Windows) dentro de una ventana MAUI, WPF o WinForms |
Rust también tiene ambas familias. Lo que le falta es el framework oficial: no hay un WPF de Rust, solo proyectos de la comunidad, y ninguno es tan maduro como WPF o JavaFX.
Renderizado nativo: egui, iced, Slint
Sección titulada «Renderizado nativo: egui, iced, Slint»Estos crates dibujan cada píxel ellos mismos, normalmente en la GPU, en una ventana abierta por winit:
| Crate | Modelo | Interfaz descrita en | Licencia | Lo más parecido |
|---|---|---|---|---|
| egui con eframe | modo inmediato: la interfaz se redibuja a partir de tus datos en cada fotograma | código Rust | MIT o Apache-2.0 | Dear ImGui, interfaces de depuración de videojuegos |
| iced | arquitectura Elm: estado, mensajes, update, view |
código Rust | MIT | Model-View-Update, como Elmish en F# |
| Slint | marcado declarativo compilado a Rust | archivos .slint |
GPLv3, libre de regalías o comercial | XAML, FXML |
Versiones en crates.io el 2026-09-15: egui y eframe 0.36.2, iced 0.14.0, Slint 1.17.1. egui e iced siguen en 0.x y sus API cambian entre versiones menores; Slint está en 1.x desde abril de 2023.
Dioxus parece una cuarta opción, con componentes al estilo de React escritos en Rust. Sin embargo, su renderizador de escritorio depende de wry y tao (dioxus-desktop 0.7.10): el mismo webview del sistema que Tauri.
egui en IX: ix-demo
Sección titulada «egui en IX: ix-demo»IX tiene una aplicación egui, crates/ix-demo (commit a7e5fbc, 2026-09-15): 23 pestañas de demos interactivas sobre los crates del workspace. Su main abre una ventana (main.rs, líneas 28-40):
fn main() -> eframe::Result<()> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([1280.0, 800.0]) .with_title("ix — ML Algorithm Explorer"), ..Default::default() }; eframe::run_native( "ix Demo", options, Box::new(|_cc| Ok(Box::new(IxApp::default()))), )}y cada demo es un struct con un método ui que egui llama cada vez que repinta (demos/stats.rs, líneas 29-37):
impl StatsDemo { pub fn ui(&mut self, ui: &mut egui::Ui) { ui.heading("Statistics (ix-math)"); ui.label("Enter comma-separated values:"); ui.text_edit_singleline(&mut self.data_text);
if ui.button("Compute").clicked() { self.compute(); }No hay XAML, ni binding, ni manejador de eventos. text_edit_singleline(&mut self.data_text) dibuja el cuadro de texto y escribe en el campo lo que el usuario teclea; button(…).clicked() dibuja el botón y devuelve true durante el fotograma en que se hizo clic. Para un desarrollador WPF, es como si OnRender reconstruyera toda la ventana a partir del view model, sin INotifyPropertyChanged.
Leer los 28 archivos (7411 líneas) de ix-demo muestra la otra cara del modo inmediato:
- El trabajo se ejecuta dentro de
ui, en el hilo que dibuja la ventana. Hay 58 manejadoresclicked()y ni un solo hilo, canal ospawn. Las cargas son pequeñas (una criba hasta 1 000 000, como mucho 2000 épocas de una red XOR), así que no se nota; un cálculo más largo congelaría todas las pestañas. Tauri lo resuelve con comandos asíncronos (lección 17) y canales (lección 18). - La pestaña Neural Network (ix-nn) no llama a
ix-nn: entrena su propia red conndarray(demos/neural_net.rs, línea 77: “demonstrates the concepts in ix-nn”). Cargo.tomlpideeframe = "0.31"cuando crates.io va por la 0.36.2, e IX no commitea unCargo.lock: cada compilación toma la última 0.31.x.
En Tauri, la misma demo sería un comando Rust por botón, que devuelve los números en JSON, y una página HTML que dibuja el gráfico con una biblioteca JavaScript. El código Rust se simplifica y la interfaz pasa a un segundo lenguaje: ese es el intercambio.
Interfaz web en un webview del sistema: Tauri
Sección titulada «Interfaz web en un webview del sistema: Tauri»Tauri retoma la idea de Electron sin incluir Chromium ni Node.js. Tu aplicación es un ejecutable Rust, el proceso núcleo (core process); el contenido de la ventana es HTML, CSS y JavaScript renderizado por el webview que el sistema operativo ya proporciona (modelo de procesos):
| Sistema | Webview | Motor |
|---|---|---|
| Windows | WebView2 | Chromium (Microsoft Edge) |
| macOS | WKWebView | WebKit (Safari) |
| Linux | WebKitGTK | WebKit |
flowchart LR subgraph core["Proceso núcleo: tu ejecutable Rust"] commands["tus comandos y tu estado"] tauri["tauri"] tao["tao: ventanas, menús"] wry["wry: webview"] end subgraph webview["Proceso del webview: WebView2, WKWebView o WebKitGTK"] ui["HTML, CSS, JavaScript"] end ui -- "invoke: petición JSON" --> tauri tauri -- "respuesta JSON, eventos, canales" --> ui tauri --> commands tauri --> tao tauri --> wry wry -. "crea" .-> webview- Solo el proceso núcleo puede tocar el sistema de archivos, la red u otros procesos. La página llega a Rust mediante comandos (
invoke, lección 17) y eventos (lección 18), serializados en JSON: es paso de mensajes, no memoria compartida. tao(un fork de winit) abre las ventanas,wryintegra el webview;tauriañade encima el IPC, la configuración, los permisos y el empaquetador.- Tres motores significan tres navegadores que probar, como en la web: una función CSS que funciona en WebView2 puede faltar en el WebKitGTK de una distribución Linux antigua.
Este curso fija Tauri 2.11.5 (publicado el 2026-07-01), la CLI de Tauri 2.11.4 y create-tauri-app 4.7.4. Una primera 3.0.0-alpha se publicó el 2026-09-13; entre sus crates hay un nuevo tauri-runtime-cef, un runtime basado en Chromium Embedded Framework. Es una alfa: el curso se queda en la 2.
La diferencia de tamaño, medida
Sección titulada «La diferencia de tamaño, medida»Electron incluye su runtime en cada aplicación. Electron 44.3.0 para Windows x64, descargado con npm install electron@44.3.0 el 2026-09-15, es un zip de 158 MB que se descomprime en 385 MB y 73 archivos; solo electron.exe pesa 246 MB — antes de cualquier línea de tu aplicación.
El explorador de acordes que construyen estas lecciones, en modo release con tauri build --no-bundle, es un único chord-explorer.exe de 4,2 MB (4 244 992 bytes) en Windows, HTML incluido; la compilación tardó 2 min 43 s. Se apoya en el runtime WebView2 instalado con Windows 11 (versión 153.0.4234.32 en mi máquina).
Un binario pequeño no significa un consumo de memoria pequeño: WebView2 arranca sus propios procesos de navegador. Con la ventana de la compilación release abierta, medido con PowerShell (Get-Process, bytes privados del proceso y de sus hijos):
Name Type Sub PrivateMB WorkingSetMB---- ---- --- --------- ------------chord-explorer 5.60 27.10msedgewebview2 browser 40.70 125.00msedgewebview2 crashpad-handler 2.50 9.70msedgewebview2 gpu-process 81.90 67.00msedgewebview2 renderer 25.10 60.10msedgewebview2 utility network.mojom.NetworkService 12.70 37.90msedgewebview2 utility storage.mojom.StorageService 8.10 18.40
total private MB: 176.6; total working set MB: 345.2; processes: 7El proceso Rust usa 5,6 MB; los otros seis procesos son de WebView2, unos 170 MB. Los working sets suman más porque cuentan varias veces las páginas compartidas entre procesos. Una aplicación Tauri es ligera en disco, no gratuita en memoria: ejecuta un motor de navegador, igual que Electron.
Requisitos previos
Sección titulada «Requisitos previos»Tauri necesita Rust (lección 1) y las bibliotecas de desarrollo del webview de cada sistema; Node.js solo hace falta para la versión npm de la CLI y para los frameworks JavaScript (requisitos previos).
- Las herramientas de compilación MSVC, ya instaladas en la lección 1; la guía de Tauri pide la carga de trabajo Desarrollo para el escritorio con C++.
- WebView2: incluido en Windows 11 y en Windows 10 desde la versión 1803. Para comprobar su versión:
(Get-ItemProperty 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}').pv153.0.4234.32- Node.js LTS para
npm(este curso: Node 24.12.0, npm 11.7.0).
En Debian y Ubuntu, la lista de la guía de Tauri:
sudo apt updatesudo apt install libwebkit2gtk-4.1-dev \ build-essential \ curl \ wget \ file \ libxdo-dev \ libssl-dev \ libayatana-appindicator3-dev \ librsvg2-devLas demás distribuciones tienen sus propios nombres de paquete en la guía. En WSL, las aplicaciones gráficas Linux necesitan WSLg (Windows 11): la ventana se abre entonces en el escritorio de Windows (por verificar: el curso aún no ha ejecutado Tauri en WSL).
xcode-select --install # suficiente para el escritorio; iOS necesita Xcode completoWKWebView forma parte de macOS: no hay nada más que instalar. La CI compila y prueba así el explorador en runners de macOS; abrir su ventana está por verificar en un Mac.
Crear un proyecto
Sección titulada «Crear un proyecto»create-tauri-app genera un proyecto. Las opciones evitan las preguntas:
npx create-tauri-app@4.7.4 hello-tauri --manager npm --template vanilla --identifier dev.learn.hello --yesTemplate created! To get started run: cd hello-tauri npm install npm run tauri android init
For Desktop development, run: npm run tauri dev
For Android development, run: npm run tauri android devLa primera sugerencia es la configuración de Android, incluso en Windows y para un proyecto de escritorio; para el escritorio, pasa al segundo bloque. La plantilla vanilla es HTML, CSS y JavaScript sin más, sin bundler (la lección 19 trata Vite y TypeScript). La estructura generada (estructura del proyecto):
hello-tauri/├── package.json ← solo la CLI de Tauri, como dependencia de desarrollo├── src/ ← el frontend: index.html, main.js, styles.css└── src-tauri/ ← un paquete Cargo normal ├── Cargo.toml ├── build.rs ← tauri_build::build(): lee la configuración, integra los iconos ├── tauri.conf.json ← ventana, identificador, carpeta del frontend, seguridad, paquete ├── capabilities/ │ └── default.json ← lo que la ventana puede llamar (lección 20) ├── icons/ └── src/ ├── main.rs ← llama a hello_tauri_lib::run() └── lib.rs ← la aplicación: comandos y BuilderTres detalles sorprenden a un desarrollador Rust:
Cargo.tomldeclaraedition = "2021", no 2024. El proyecto del curso usa 2024, como en la lección 1, y compila igual.- La biblioteca se llama
hello_tauri_liby se compila como["staticlib", "cdylib", "rlib"]: en iOS y Android la aplicación es una biblioteca cargada por la plataforma, así quemain.rssolo llama alib.rs, donde vive todo. El comentario explica el sufijo_lib: una biblioteca y un binario con el mismo nombre chocan en Windows. main.rsempieza con#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]: sin eso, una compilación release abriría una ventana de consola junto a la aplicación.
tauri.conf.json es para Tauri lo que App.xaml más el .csproj son para WPF (referencia de configuración):
{ "$schema": "https://schema.tauri.app/config/2", "productName": "hello-tauri", "version": "0.1.0", "identifier": "dev.learn.hello", "build": { "frontendDist": "../src" }, "app": { "withGlobalTauri": true, "windows": [ { "title": "hello-tauri", "width": 800, "height": 600 } ], "security": { "csp": null } }, "bundle": { "active": true, "targets": "all", "icon": [ "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" ] }}frontendDistes la carpeta de archivos estáticos integrados en el ejecutable; un framework con servidor de desarrollo añadiríadevUrl.withGlobalTauri: trueexpone la API JavaScript comowindow.__TAURI__, de modo que una página sin bundler puede llamar awindow.__TAURI__.core.invoke."csp": nulldesactiva la Content Security Policy. El proyecto del curso define una, y la lección 20 explica por qué el valor por defecto de la plantilla merece una segunda mirada.
Ejecutarlo: tauri dev
Sección titulada «Ejecutarlo: tauri dev»Desde la carpeta del curso, npm install instala la CLI fijada por package-lock.json, y luego npx tauri dev compila y arranca la aplicación:
cd code/rust-for-csharp-java/l16-taurinpm installnpx tauri dev Running DevCommand (`cargo run --no-default-features --color always --`) Info Watching C:\Users\spare\source\repos\learn\code\rust-for-csharp-java\l16-tauri\src-tauri for changes... Info Watching C:\Users\spare\source\repos\learn\code\rust-for-csharp-java\l16-tauri\theory for changes... Compiling chord-explorer v0.1.0 (C:\Users\spare\source\repos\learn\code\rust-for-csharp-java\l16-tauri\src-tauri) Finished `dev` profile [unoptimized + debuginfo] target(s) in 25.84s Running `C:\Users\spare\source\repos\learn\code\rust-for-csharp-java\l16-tauri\target\debug\chord-explorer.exe`- Por debajo es
cargo run: la primera ejecución compila todas las dependencias de Tauri y tarda minutos; la ejecución de arriba solo recompiló la aplicación. - La CLI vigila
src-tauriy los crates de los que depende por ruta, aquítheory. Cambiar el título de la ventana entauri.conf.jsonrecompiló la aplicación (20,84 s) y la reinició con el nuevo título. - Sin
devUrl, la CLI sirve ella misma la carpetafrontendDist: la dirección de la página en el webview erahttp://127.0.0.1:1430/.
La aplicación del curso: un explorador de acordes
Sección titulada «La aplicación del curso: un explorador de acordes»Las lecciones siguientes construyen una sola aplicación, en el espíritu de Guitar Alchemist: deletrear un acorde (Bbm7 es Bb Db F Ab), transponerlo, listar los acordes de una tonalidad, guardar favoritos y buscar todas las formas de tocar un acorde en una guitarra.
La teoría musical es un crate aparte que no sabe nada de Tauri (l16-tauri/Cargo.toml):
[workspace]resolver = "3"members = ["theory", "src-tauri"]l16-tauri/├── Cargo.toml ← el workspace (lección 10)├── package.json ← @tauri-apps/cli 2.11.4, con su package-lock.json├── theory/ ← notas, acordes, escalas, posiciones: Rust sin más, sin dependencias├── src-tauri/ ← la aplicación Tauri: comandos y estado que llaman a theory└── ui/ ← index.html, main.js, styles.cssEs la separación en capas que usarías en .NET, con una biblioteca de clases referenciada por el proyecto WPF, o en Java, con un módulo de dominio bajo el de JavaFX: la lógica se puede probar sin ventana. cargo test -p theory ejecuta sus 15 tests sin compilar ninguno de los otros 236 crates que la aplicación Tauri arrastra en Windows (cargo tree):
running 15 teststest chord::tests::reports_what_is_wrong ... oktest chord::tests::every_quality_round_trips ... oktest chord::tests::spells_chords_with_the_right_letters ... oktest chord::tests::mask_ignores_spelling ... oktest note::tests::rejects_what_is_not_a_note ... oktest scale::tests::one_letter_per_degree ... oktest note::tests::parses_and_prints_notes ... oktest voicing::tests::rejects_muted_strings_between_sounding_ones ... oktest note::tests::spells_by_letter_then_semitones ... oktest scale::tests::diatonic_triads ... oktest chord::tests::transposes_with_common_spellings ... oktest note::tests::enharmonic_notes_share_a_pitch_class ... oktest voicing::tests::reports_every_batch_and_stops_when_cancelled ... oktest voicing::tests::every_result_is_a_root_position_voicing ... oktest voicing::tests::finds_the_open_chords ... ok
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.13sPuntos clave
Sección titulada «Puntos clave»- Las interfaces de escritorio en Rust forman dos familias: renderizado nativo (egui, iced, Slint) e interfaz web en un webview (Tauri, y Dioxus en el escritorio).
- egui redibuja la interfaz a partir de tus datos en cada fotograma; es sencillo de escribir, pero el trabajo que pones en
uise ejecuta en el hilo de dibujo. - Una aplicación Tauri es un proceso núcleo en Rust más el webview del sistema: WebView2, WKWebView o WebKitGTK. La interfaz habla con Rust por paso de mensajes, en JSON.
- No incluir Chromium deja el ejecutable en unos pocos megabytes en lugar de cientos, pero los procesos del webview siguen usando memoria, y hay que probar tres motores.
src-tauries un paquete Cargo normal;tauri.conf.jsondescribe las ventanas, el frontend y los ajustes de seguridad.
Ejercicios
Sección titulada «Ejercicios»- ¿Qué webview, y qué versión de motor, renderiza una ventana Tauri en tu máquina? Averígualo desde JavaScript, en las herramientas de desarrollo de la ventana (clic derecho, Inspeccionar, en una compilación debug).
Solución
navigator.userAgent describe el motor. En la ventana del explorador, en Windows:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36 Edg/153.0.0.0El token Edg/ es WebView2; su versión es la que encontramos arriba en el registro. En macOS el user agent contiene AppleWebKit sin Chrome, y en Linux la versión de WebKitGTK.
- Cambia la ventana del explorador para que se abra a 1100 × 800, no pueda hacerse más pequeña que 700 × 500 y se titule Chords.
Solución
En src-tauri/tauri.conf.json, el objeto de ventana acepta minWidth y minHeight (WindowConfig):
"windows": [ { "title": "Chords", "width": 1100, "height": 800, "minWidth": 700, "minHeight": 500 }]tauri dev detecta el cambio y reinicia la aplicación. La configuración se lee en tiempo de compilación con tauri::generate_context!(), así que una compilación release también debe recompilarse.
- ¿Por qué el crate
theoryno depende deserde, si todos los resultados llegan a JavaScript en JSON?
Solución
Porque el JSON es asunto de la frontera, no de la teoría musical. src-tauri convierte los tipos de theory en pequeños structs serializables (ChordView, lección 17), el equivalente de los DTO en un controlador ASP.NET Core o Spring. theory sigue siendo utilizable desde una herramienta de línea de comandos, un test o una aplicación egui, y sus tests compilan en un segundo más o menos. Añadir serde tras una feature opcional de Cargo (lección 10) también sería razonable si varios frontends necesitaran el mismo JSON.