Rust Cheatsheet para Principiantes (desde C++/Java)
Este cheatsheet se centra en los conceptos fundamentales que necesitas para empezar a ser productivo en Rust, basándose en la tabla que proporcionaste.
1. Rust Básico
Variables y Mutabilidad
Rust prioriza la inmutabilidad por defecto. Usa let para declarar variables. Para hacerlas mutables, añade mut. Los tipos se infieren a menudo, pero puedes especificarlos.
- Inmutable:
#![allow(unused)] fn main() { let x: i32 = 5; // x es inmutable let y = 10; // Tipo i32 inferido, y es inmutable // x = 6; // Error! No se puede reasignar una variable inmutable } - Mutable:
#![allow(unused)] fn main() { let mut z = 15; // z es mutable z = 20; // ¡Ok! println!("z es: {}", z); } - Constantes (siempre inmutables, tipo debe ser anotado, evaluadas en tiempo de compilación):
#![allow(unused)] fn main() { const MAX_POINTS: u32 = 100_000; } - Shadowing (puedes declarar una nueva variable con el mismo nombre, incluso cambiando el tipo):
#![allow(unused)] fn main() { let spaces = " "; let spaces = spaces.len(); // Ahora 'spaces' es un número (3) }
Tipos de Datos Comunes
- Enteros:
i8,u8,i16,u16,i32,u32,i64,u64,i128,u128,isize,usize(tamaño de puntero). - Flotantes:
f32,f64(por defectof64). - Booleanos:
bool(true,false). - Caracteres:
char(Unicode, 4 bytes). - Tuplas: Colección fija de diferentes tipos.
#![allow(unused)] fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); let (x, y, z) = tup; // Destructuración println!("El valor de y es: {}", y); // 6.4 println!("El primer valor es: {}", tup.0); // Acceso por índice } - Arrays: Colección fija del mismo tipo, en el stack.
#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; let first = a[0]; // let b: [i32; 5] = [1, 2, 3, 4, 5]; // Con anotación de tipo y tamaño // a[0] = 10; // Error si 'a' no es mutable } - Strings:
&str(string slice): Inmutable, referencia a datos de string (usualmente en el binario o en el heap).#![allow(unused)] fn main() { let s_literal = "hola"; // &str }String: Mutable, buffer de string en el heap. Similar astd::stringen C++ oStringen Java.#![allow(unused)] fn main() { let mut s_heap = String::from("hola"); s_heap.push_str(", mundo!"); println!("{}", s_heap); // hola, mundo! }
Funciones
Declaradas con fn. Los tipos de los parámetros y el tipo de retorno (si lo hay) deben ser especificados. La última expresión en una función es su valor de retorno implícito si no se usa return.
fn saludar(nombre: &str) { println!("Hola, {}!", nombre); } fn sumar(a: i32, b: i32) -> i32 { a + b // No se necesita ';' ni 'return' si es la última expresión } fn main() { saludar("Mundo"); let resultado = sumar(5, 3); println!("5 + 3 = {}", resultado); }
Control de Flujo
if/else if/else: Similar a C++/Java. Las condiciones deben serbool.ifes una expresión.#![allow(unused)] fn main() { let numero = 6; if numero % 4 == 0 { println!("El número es divisible por 4"); } else if numero % 3 == 0 { println!("El número es divisible por 3"); } else { println!("El número no es divisible ni por 4 ni por 3"); } let condicion = true; let valor = if condicion { 5 } else { 6 }; // 'if' como expresión println!("El valor es: {}", valor); }- Bucles:
loop: Bucle infinito (usabreakpara salir).#![allow(unused)] fn main() { let mut contador = 0; let resultado_loop = loop { contador += 1; if contador == 10 { break contador * 2; // 'break' puede devolver un valor } }; println!("El resultado del loop es {}", resultado_loop); // 20 }while: Bucle condicional.#![allow(unused)] fn main() { let mut numero = 3; while numero != 0 { println!("{}!", numero); numero -= 1; } println!("¡DESPEGUE!"); }for: Para iterar sobre colecciones (más común y seguro).#![allow(unused)] fn main() { let a = [10, 20, 30, 40, 50]; for elemento in a.iter() { // .iter() crea un iterador sobre referencias println!("El valor es: {}", elemento); } for numero in (1..4).rev() { // Rango (1, 2, 3) en reversa println!("{}!", numero); } }
Módulos
Para organizar el código.
// En lib.rs o main.rs mod mi_modulo { pub fn funcion_publica() { println!("Llamada a funcion_publica()"); funcion_privada(); } fn funcion_privada() { println!("Llamada a funcion_privada()"); } pub mod sub_modulo { pub fn otra_funcion() { println!("Llamada a otra_funcion() en sub_modulo"); } } } fn main() { // Ruta completa crate::mi_modulo::funcion_publica(); // Usando 'use' use crate::mi_modulo::sub_modulo::otra_funcion; otra_funcion(); // Si 'mi_modulo' está en otro archivo llamado 'mi_modulo.rs' // mod mi_modulo; // En main.rs o lib.rs para declarar el módulo // mi_modulo::funcion_publica(); }
crate: Raíz del crate (paquete).pub: Hace un ítem público. Por defecto, todo es privado.use: Importa ítems a un scope.
2. Gestión de Paquetes con Cargo
Cargo es el gestor de paquetes y sistema de construcción de Rust.
- Crear un nuevo proyecto:
cargo new mi_proyecto # Crea una aplicación binaria cargo new --lib mi_libreria # Crea una librería - Estructura típica del proyecto:
mi_proyecto/ ├── Cargo.toml # Manifiesto del paquete (metadatos, dependencias) ├── src/ │ └── main.rs # Código fuente principal para binarios │ # O lib.rs para librerías - Comandos comunes de Cargo:
cargo build: Compila el proyecto (entarget/debug/).cargo build --release: Compila con optimizaciones (entarget/release/).cargo run: Compila y ejecuta el binario.cargo test: Ejecuta las pruebas.cargo check: Comprueba el código sin generar un ejecutable (más rápido).cargo doc --open: Genera y abre la documentación.cargo add <crate_name>: Añade una dependencia aCargo.toml.
Cargo.toml(ejemplo básico):[package] name = "mi_proyecto" version = "0.1.0" edition = "2021" # Edición de Rust [dependencies] # rand = "0.8.5" # Ejemplo de dependencia de crates.io # ratatui = { version = "0.26.0", features = ["crossterm"] } # crossterm = "0.27.0"
3. Propiedad y Préstamos (Ownership & Borrowing)
Este es el concepto más distintivo de Rust. Asegura la seguridad de memoria sin un recolector de basura.
Reglas de Propiedad (Ownership)
- Cada valor en Rust tiene una variable que es su dueña (owner).
- Solo puede haber un dueño a la vez.
- Cuando el dueño sale del ámbito (scope), el valor se libera (dropped).
#![allow(unused)] fn main() { { let s1 = String::from("hola"); // s1 es dueña de "hola" // let s2 = s1; // ¡MOVIMIENTO! s1 ya no es válida. // Similar a std::unique_ptr en C++ (sin copia implícita) // Para tipos simples como i32, se copian (implementan el trait 'Copy') let s2 = s1.clone(); // Copia profunda (heap data copiado) println!("s1 = {}, s2 = {}", s1, s2); // Ambas válidas } // s1 (si no fue movida) y s2 salen del scope, la memoria se libera. }
Préstamos y Referencias (Borrowing & References)
Permiten acceder a datos sin tomar posesión.
- Referencias Inmutables (
&T):- Puedes tener múltiples referencias inmutables a un dato.
- No puedes modificar el dato a través de ellas.
#![allow(unused)] fn main() { let s1 = String::from("hola"); let len = calcular_longitud(&s1); // Se presta s1 inmutablemente println!("La longitud de '{}' es {}.", s1, len); fn calcular_longitud(s: &String) -> usize { // s es una referencia a un String s.len() } // s sale del scope, pero no libera nada porque no es dueña } - Referencias Mutables (
&mut T):- Solo puedes tener una referencia mutable a un dato en un scope particular.
- Esto previene data races en tiempo de compilación.
- No puedes tener referencias inmutables si existe una mutable.
Restricción importante:#![allow(unused)] fn main() { let mut s = String::from("hola"); cambiar(&mut s); println!("{}", s); // "hola, mundo" fn cambiar(algun_string: &mut String) { algun_string.push_str(", mundo"); } }#![allow(unused)] fn main() { let mut s = String::from("hello"); let r1 = &s; // Referencia inmutable, OK let r2 = &s; // Otra referencia inmutable, OK // let r3 = &mut s; // ¡ERROR! No puedes tener una referencia mutable mientras existan inmutables // println!("{}, {}, and {}", r1, r2, r3); let mut s_mut = String::from("hello"); let r_mut1 = &mut s_mut; // let r_mut2 = &mut s_mut; // ¡ERROR! Solo una referencia mutable a la vez // println!("{}, {}", r_mut1, r_mut2); }
Lifetimes (Tiempos de Vida) 'a
Aseguran que las referencias sean siempre válidas. A menudo son inferidos por el compilador. Los necesitas explícitamente cuando las relaciones de lifetimes entre referencias no son obvias para el compilador, especialmente en firmas de funciones y structs que contienen referencias.
- Objetivo: Evitar dangling references (referencias que apuntan a memoria que ha sido liberada).
- Ejemplo donde se necesitan:
La idea es que el dato referenciado por// Devuelve una referencia, el compilador necesita saber si vive tanto como 'x' o 'y' fn el_mas_largo<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("cadena larga es larga"); { let string2 = String::from("xyz"); let resultado = el_mas_largo(string1.as_str(), string2.as_str()); println!("La cadena más larga es {}", resultado); } // string2 se libera aquí. Si resultado apuntara a string2, sería un dangling reference. // Pero el lifetime 'a' del resultado está atado al más corto de string1 y string2. }'adebe vivir al menos tanto como la referencia anotada con'a.
4. POO en Rust (Estilo Rust)
Rust no tiene "clases" como Java/C++, pero ofrece estructuras (structs), enumeraciones (enums), y comportamientos (traits) para lograr encapsulación y polimorfismo.
struct (Estructuras)
Agrupan datos relacionados. Similar a struct en C o los campos de una clase en Java/C++.
struct Usuario { activo: bool, nombre_usuario: String, email: String, contador_sesion: u64, } fn main() { let mut usuario1 = Usuario { email: String::from("alguien@ejemplo.com"), nombre_usuario: String::from("alguien123"), activo: true, contador_sesion: 1, }; usuario1.email = String::from("otroemail@ejemplo.com"); println!("Email: {}", usuario1.email); let usuario2 = construir_usuario( String::from("usuario2@ejemplo.com"), String::from("usuario2") ); } fn construir_usuario(email: String, nombre_usuario: String) -> Usuario { Usuario { email, // Abreviatura si el nombre del parámetro y el campo son iguales nombre_usuario, activo: true, contador_sesion: 1, } } // Structs tupla (sin nombres de campo) struct Color(i32, i32, i32); struct Punto(i32, i32, i32); let negro = Color(0, 0, 0); let origen = Punto(0, 0, 0); println!("Primer valor de negro: {}", negro.0);
impl (Implementación de Métodos)
Define métodos para structs y enums.
struct Rectangulo { ancho: u32, alto: u32, } // Bloque de implementación para Rectangulo impl Rectangulo { // Método (toma &self, &mut self, o self) fn area(&self) -> u32 { // &self es una referencia inmutable a la instancia self.ancho * self.alto } fn puede_contener(&self, otro: &Rectangulo) -> bool { self.ancho > otro.ancho && self.alto > otro.alto } // Función asociada (no toma self), a menudo usada como constructor // Similar a un método estático en Java/C++ fn cuadrado(tamano: u32) -> Self { // Self es un alias para Rectangulo Self { ancho: tamano, alto: tamano } } } fn main() { let rect1 = Rectangulo { ancho: 30, alto: 50 }; let rect2 = Rectangulo { ancho: 10, alto: 40 }; let rect_cuadrado = Rectangulo::cuadrado(25); // Llamada a función asociada println!("El área del rectángulo es {} pixeles cuadrados.", rect1.area()); println!("¿Puede rect1 contener a rect2? {}", rect1.puede_contener(&rect2)); println!("Área del cuadrado: {}", rect_cuadrado.area()); }
enum (Enumeraciones)
Tipos que pueden ser uno de varios valores posibles (variantes). Las variantes pueden tener datos asociados. Son mucho más potentes que los enums de C/C++ y Java (más cercanos a uniones discriminadas o tipos algebraicos de datos).
enum Mensaje { Salir, // Sin datos asociados Mover { x: i32, y: i32 }, // Con campos nombrados (como un struct) Escribir(String), // Con un String CambiarColor(i32, i32, i32), // Con una tupla de datos } impl Mensaje { fn procesar(&self) { match self { Mensaje::Salir => println!("Saliendo..."), Mensaje::Mover { x, y } => { println!("Moviendo a x: {}, y: {}", x, y); } Mensaje::Escribir(texto) => { println!("Mensaje de texto: {}", texto); } Mensaje::CambiarColor(r, g, b) => { println!("Cambiando color a R:{}, G:{}, B:{}", r, g, b); } } } } fn main() { let msg1 = Mensaje::Mover { x: 10, y: 20 }; let msg2 = Mensaje::Escribir(String::from("Hola enum")); msg1.procesar(); msg2.procesar(); }
trait (Rasgos)
Definen un conjunto de métodos que un tipo puede implementar. Similar a interfaces en Java o clases base puramente virtuales en C++. Permiten el polimorfismo.
// Definición de un trait pub trait Resumible { fn resumir_autor(&self) -> String; // Método que debe ser implementado fn resumir(&self) -> String { // Método con implementación por defecto format!("(Leer más de {}...)", self.resumir_autor()) } } pub struct Noticia { pub titular: String, pub autor: String, pub contenido: String, } // Implementación del trait Resumible para Noticia impl Resumible for Noticia { fn resumir_autor(&self) -> String { format!("@{}", self.autor) } // Podríamos sobreescribir resumir() aquí si quisiéramos } pub struct Tweet { pub nombre_usuario: String, pub contenido: String, pub respuesta: bool, pub retweet: bool, } impl Resumible for Tweet { fn resumir_autor(&self) -> String { format!("@{}", self.nombre_usuario) } fn resumir(&self) -> String { format!("{}: {}", self.nombre_usuario, self.contenido) } } // Polimorfismo usando traits (parámetros genéricos con trait bounds) pub fn notificar<T: Resumible>(item: &T) { println!("¡Noticia de última hora! {}", item.resumir()); } // Polimorfismo usando trait objects (dinámico) pub fn notificar_dinamico(item: &dyn Resumible) { println!("¡Noticia dinámica! {}", item.resumir()); } fn main() { let tweet = Tweet { nombre_usuario: String::from("caballo_ebooks"), contenido: String::from("por supuesto, como ustedes saben, la gente"), respuesta: false, retweet: false, }; let articulo = Noticia { titular: String::from("¡Los pingüinos ganan la Stanley Cup!"), autor: String::from("Iceburgh"), contenido: String::from("El equipo de hockey de Pittsburgh, los Pingüinos..."), }; println!("1 nuevo tweet: {}", tweet.resumir()); println!("Nuevo artículo disponible: {}", articulo.resumir()); notificar(&tweet); notificar_dinamico(&articulo); }
5. Tratamiento de Errores
Rust no tiene excepciones como Java o C++. En su lugar, usa tipos para representar posibles errores.
Option<T>
Para valores que pueden ser opcionales (o estar ausentes). Similar a Optional<T> en Java 8+.
- Variantes:
Some(T)(el valor está presente) oNone(el valor está ausente).
fn encontrar_caracter(texto: &str, caracter_buscado: char) -> Option<usize> { for (indice, caracter_actual) in texto.char_indices() { if caracter_actual == caracter_buscado { return Some(indice); // Valor encontrado } } None // Valor no encontrado } fn main() { let texto = "hola mundo"; match encontrar_caracter(texto, 'm') { Some(indice) => println!("'m' encontrada en el índice: {}", indice), None => println!("'m' no encontrada."), } match encontrar_caracter(texto, 'z') { Some(indice) => println!("'z' encontrada en el índice: {}", indice), None => println!("'z' no encontrada."), } // 'if let' es una forma concisa de manejar una variante de Option/Result if let Some(indice) = encontrar_caracter(texto, 'o') { println!("'o' encontrada con if let en: {}", indice); } // Métodos útiles de Option: // unwrap(): Devuelve el valor en Some(T) o hace panic! si es None (¡usar con cuidado!) // unwrap_or(default_value): Devuelve el valor o un valor por defecto. // unwrap_or_else(closure): Devuelve el valor o el resultado de una clausura. // map(closure): Aplica una función al valor dentro de Some. // and_then(closure): Encadena operaciones que devuelven Option. let quizas_numero: Option<i32> = Some(5); let _numero_mas_uno = quizas_numero.map(|n| n + 1); // Some(6) let _otro_numero = None::<i32>.unwrap_or(0); // 0 }
Result<T, E>
Para operaciones que pueden fallar (éxito o error). T es el tipo del valor en caso de éxito, E es el tipo del error.
- Variantes:
Ok(T)(operación exitosa) oErr(E)(operación fallida).
use std::fs::File; use std::io::{self, Read}; // Función que devuelve un Result fn leer_archivo(nombre_archivo: &str) -> Result<String, io::Error> { let mut f = match File::open(nombre_archivo) { Ok(archivo) => archivo, Err(e) => return Err(e), // Propaga el error temprano }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } } // Usando el operador '?' para propagar errores (más idiomático) // '?' solo puede usarse en funciones que devuelven Result o Option. fn leer_archivo_con_interrogante(nombre_archivo: &str) -> Result<String, io::Error> { let mut f = File::open(nombre_archivo)?; // Si es Err, retorna Err(e) desde esta función let mut s = String::new(); f.read_to_string(&mut s)?; // Si es Err, retorna Err(e) Ok(s) // Si todo va bien, retorna Ok(s) // Incluso más corto: // std::fs::read_to_string(nombre_archivo) } fn main() { match leer_archivo_con_interrogante("hola.txt") { Ok(contenido) => println!("Contenido del archivo: {}", contenido), Err(error) => println!("Error al leer el archivo: {:?}", error), } // expect(): Similar a unwrap(), pero con un mensaje de panic personalizado. // let _contenido = File::open("no_existe.txt").expect("Falló al abrir no_existe.txt"); }
El operador ? desenvuelve Ok(T) a T o retorna Err(E) de la función actual.
6. Asincronía (Básico) (Opcional, según tu proyecto TUI)
Si tu TUI necesita realizar operaciones de I/O (red, disco) sin bloquear el hilo principal, necesitarás asincronía.
async fn: Define una función asíncrona. Devuelve unFuture..await: Espera a que unFuturese complete sin bloquear el hilo. Solo se puede usar dentro de unaasync fn.
// Este es un ejemplo conceptual. Necesitarías un runtime como tokio o async-std. // Añade a Cargo.toml: // tokio = { version = "1", features = ["full"] } /* async fn obtener_datos_web(url: &str) -> Result<String, reqwest::Error> { // reqwest es una librería popular para HTTP, también asíncrona let respuesta = reqwest::get(url).await?; let cuerpo = respuesta.text().await?; Ok(cuerpo) } async fn tarea_larga() { println!("Iniciando tarea larga..."); // Simula trabajo, como una llamada de red tokio::time::sleep(std::time::Duration::from_secs(2)).await; println!("Tarea larga completada."); } // En tu función main (o una función marcada con el macro del runtime) #[tokio::main] // Si usas tokio async fn main() { println!("Iniciando programa asíncrono."); let handle1 = tokio::spawn(async { // Ejecuta en una nueva "tarea" (green thread) tarea_larga().await; }); let handle2 = tokio::spawn(async { match obtener_datos_web("https://www.rust-lang.org").await { Ok(pagina) => println!("Página obtenida (primeros 100 chars): {:.100}", pagina), Err(e) => eprintln!("Error al obtener la página: {}", e), } }); println!("Tareas iniciadas, esperando..."); // Espera a que todas las tareas spawneadas terminen let _ = tokio::try_join!(handle1, handle2); println!("Programa asíncrono finalizado."); } */
- Runtimes asíncronos (
tokio,async-std): Proveen el ejecutor para losFutures, manejo de I/O, timers, etc.tokioes muy popular, especialmente para networking.async-stdbusca ser un análogo asíncrono de la librería estándar.
7. Ratatui + Crossterm (Específico para TUI)
Estas bibliotecas te ayudarán a construir tu Interfaz de Usuario en Terminal (TUI).
- Crossterm: Proporciona funcionalidades de bajo nivel para interactuar con la terminal (manipulación del cursor, colores, entrada de teclado/ratón, modo raw). Ratatui lo usa como backend.
- Ratatui: Es una biblioteca para construir TUIs, inspirada en React. Se basa en dibujar "widgets" en un "buffer" y luego mostrar ese buffer en la terminal.
- Ciclo principal (conceptual):
- Inicializar la terminal (usando Crossterm).
- Entrar en un bucle:
a. Leer eventos de entrada (teclado, ratón) de forma no bloqueante (Crossterm).
b. Actualizar el estado de tu aplicación según la entrada.
c. Dibujar la UI en un
Framede Ratatui: i. DefinirLayoutspara dividir el área de la terminal. ii. RenderizarWidgetsen las áreas del layout. d. Si el usuario quiere salir, romper el bucle. - Restaurar la terminal.
- Ciclo principal (conceptual):
Conceptos Clave de Ratatui:
Terminal: El objeto principal para interactuar con la terminal.Frame: Se pasa a tu función de dibujado en cada iteración, es donde dibujas.Layout: Define cómo dividir un área rectangular en sub-áreas.Direction:HorizontaloVertical.Constraint: Define el tamaño de cada celda del layout (Percentage,Length,Min,Max).
#![allow(unused)] fn main() { // Ejemplo de Layout // use ratatui::layout::{Layout, Direction, Constraint, Rect}; // fn ui(frame: &mut Frame) { // let chunks = Layout::default() // .direction(Direction::Vertical) // .margin(1) // .constraints( // [ // Constraint::Percentage(10), // 10% para el título // Constraint::Percentage(80), // 80% para el contenido // Constraint::Percentage(10), // 10% para el pie // ] // .as_ref(), // ) // .split(frame.size()); // frame.size() es el Rect de toda la terminal // // chunks[0] es el Rect para el título, chunks[1] para contenido, etc. // } }Widget: Componentes visuales. Sonstructsque implementan el traitWidget.Block: Un contenedor con bordes opcionales y título. A menudo envuelve a otros widgets.Paragraph: Para mostrar texto, con opciones de alineación y autoajuste.List: Para mostrar una lista de ítems.Tabs: Para crear una interfaz con pestañas.- Muchos más:
Table,Chart,Gauge,Sparkline, etc.
#![allow(unused)] fn main() { // Ejemplo de Widget (dentro de la función ui) // use ratatui::widgets::{Block, Borders, Paragraph}; // use ratatui::text::Text; // // // En chunks[0] (definido arriba) // let titulo = Paragraph::new(Text::styled("Mi TUI con Ratatui", Style::default().fg(Color::Yellow))) // .block(Block::default().borders(Borders::ALL)) // .alignment(Alignment::Center); // frame.render_widget(titulo, chunks[0]); // // // En chunks[1] // let contenido_texto = "Este es el contenido principal...\nPresiona 'q' para salir."; // let parrafo_contenido = Paragraph::new(contenido_texto) // .block(Block::default().title("Contenido").borders(Borders::ALL)); // frame.render_widget(parrafo_contenido, chunks[1]); }Style: Para definir colores (foreground, background) y modificadores (bold, italic).- Eventos (con Crossterm):
#![allow(unused)] fn main() { // use crossterm::event::{self, Event as CrosstermEvent, KeyCode, KeyEvent}; // loop { // // Esperar un evento por un tiempo máximo (para no bloquear indefinidamente) // if crossterm::event::poll(std::time::Duration::from_millis(50))? { // if let CrosstermEvent::Key(key_event) = event::read()? { // match key_event.code { // KeyCode::Char('q') => break, // Salir del bucle // KeyCode::Up => { /* mover selección hacia arriba */ }, // KeyCode::Down => { /* mover selección hacia abajo */ }, // // ... otros KeyCode // _ => {} // } // } else if let CrosstermEvent::Mouse(mouse_event) = event::read()? { // // Procesar eventos de ratón (posición, clics) // // mouse_event.kind, mouse_event.column, mouse_event.row // } // } // // Lógica de la app y dibujar UI aquí // // terminal.draw(|f| ui(f, &app_state))?; // } }
Configuración inicial para Ratatui + Crossterm:
- Añade a
Cargo.toml:[dependencies] ratatui = { version = "0.27.0", features = ["crossterm"] } # O la versión más reciente crossterm = "0.27.0" # O la versión más reciente - Código básico de inicialización/restauración:
// En tu main.rs /* use std::io; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use crossterm::{terminal::{enable_raw_mode, disable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, execute}; fn main() -> Result<(), Box<dyn std::error::Error>> { // Setup terminal enable_raw_mode()?; // Entrar en modo raw para control total de entrada let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen)?; // Cambiar a una pantalla alternativa let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; // Bucle principal de la aplicación aquí... // loop { // terminal.draw(|f| { // // ui(f, &app_state); // Tu función de dibujado // })?; // // Manejo de eventos aquí... // // if quit { break; } // } // Restaurar terminal disable_raw_mode()?; execute!(terminal.backend_mut(), LeaveAlternateScreen)?; // terminal.show_cursor()?; // Si lo ocultaste Ok(()) } */