Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 defecto f64).
  • 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 a std::string en C++ o String en 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 ser bool. if es 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 (usa break para 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 (en target/debug/).
    • cargo build --release: Compila con optimizaciones (en target/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 a Cargo.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)

  1. Cada valor en Rust tiene una variable que es su dueña (owner).
  2. Solo puede haber un dueño a la vez.
  3. 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.
    #![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");
    }
    }
    Restricción importante:
    #![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:
    // 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.
    }
    La idea es que el dato referenciado por 'a debe 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) o None (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) o Err(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 un Future.
  • .await: Espera a que un Future se complete sin bloquear el hilo. Solo se puede usar dentro de una async 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 los Futures, manejo de I/O, timers, etc.
    • tokio es muy popular, especialmente para networking.
    • async-std busca 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):
      1. Inicializar la terminal (usando Crossterm).
      2. 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 Frame de Ratatui: i. Definir Layouts para dividir el área de la terminal. ii. Renderizar Widgets en las áreas del layout. d. Si el usuario quiere salir, romper el bucle.
      3. Restaurar la terminal.

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: Horizontal o Vertical.
    • 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. Son structs que implementan el trait Widget.
    • 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:

  1. 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
    
  2. 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(())
    }
    */