Primeros pasos en Rust - Ejemplo

Apuntes de Rust

Para concluir esta primera parte de los apuntes de Rust, vamos a desarrollar una aplicación sencilla que nos permitirá poner en práctica los elementos del lenguaje presentados en las publicaciones anteriores.

Vamos a desarrollar una aplicacion que brinde información sobre jugadores de la NBA, es una aplicación de consola escrita en Rust . A continuación, se describe brevemente lo que hace este programa:

Presenta un menú de opciones: El programa muestra un menú en la consola con varias opciones para que el usuario elija qué operación realizar.

  • Listar jugadores: Lista la información se almacenada de los jugadores registrados en la aplicación.

  • Buscar jugador por nombre: A partir de un nombre de jugador, la aplicación buscará en los datos almacenados si existe algún jugador con ese nombre y mostrará la información detallada del jugador encontrado, incluyendo su nombre, equipo, posición y puntos por juego.

  • Buscar jugador con mejor promedio de anotación: La aplicación buscará en los datos almacenados y mostrará la información detallada del jugador con mejor promedio de anotación, incluyendo su nombre, equipo, posición y puntos por juego.

  • Salir: Si el usuario selecciona la opción "Salir", el programa finaliza y se cierra la aplicación.

El programa utiliza el enfoque de programación modular, dividiendo la lógica en diferentes archivos y funciones para facilitar la organización y reutilización del código. Además, hace uso de estructuras de datos y estructuras de control como loop y match para realizar diferentes acciones según la opción seleccionada por el usuario.

Paso 1 - Crear un proyecto con cargo

Ejecutar cargo new proyecto, recordemos que cargo se encarga de crear un nuevo proyecto en Rust.

cargo new proyecto
cd proyecto

Cargo creará la siguiente jerarquía de directorios.

Cargo crea un archivo main.rs en el directorio src. Este archivo main.rs contiene una función main() que imprime un “¡Hola, mundo!”, por medio de la instrucción println!().

Recuerde, utilice el comando cargo build para compilar y el comando cargo run para ejecutar.

cargo build
cargo run

Paso 2 - La estructura jugador

En el archivo main.rs, defina la estructura JugadorNBA, similar al artículo anterior, esta contiene unos campos adicionales.

struct JugadorNBA {
    nombre: String,
    posicion: String,
    equipo: String,    
    partidos_jugados: i8,
    minutos_por_juego: f32,
    puntos_por_juego: f32,
}

Escriba una función en el archivo main.rs para crear un jugador teniendo en cuenta los campos definidos en la estructura JugadorNBA.

fn crear_jugador(nombre: String, posicion: String, equipo: String, partidos_jugados: i8, 
                 minutos_por_juego: f32, puntos_por_juego: f32) -> JugadorNBA {
    JugadorNBA {
        nombre,
        posicion,
        equipo,    
        partidos_jugados,
        minutos_por_juego,
        puntos_por_juego,
    }
}

Para finalizar este paso, desarrolle una función en el archivo main.rs para imprimir en pantalla la información detallada de un jugador teniendo en cuenta los campos definidos en la estructura JugadorNBA.

fn imprimir_jugador(jugador: &JugadorNBA) {
    println!("\n*** Información del jugador ***");
    println!("Nombre: {}", jugador.nombre);
    println!("Equipo: {}", jugador.equipo);
    println!("Posición: {}", jugador.posicion);    
    println!("Total partidos jugados: {}", jugador.partidos_jugados);
    println!("Promedio de minutos por partido: {}", jugador.minutos_por_juego);
    println!("Promedio de puntos por partido: {}", jugador.puntos_por_juego);
}

Como puede observar, la función recibe por parámetro la referencia a un jugador. Mover una estructura puede ser costoso, además, puede ser problemático si queremos usar una instancia de JugadorNBA en varios partes de nuestro código sin ceder la propiedad.

Recordemos que se crea una referencia utilizando el operador & en una variable, en este caso la instancia de JugadorNBA, y apunta al valor que la instancia posee.

Los campos de la estructura se imprimen especificando la instancia de jugador y accediendo al campo por medio del operador punto.

Paso 3 - Crear jugadores

En la función main cree dos jugadores, para esto es práctico invocar la función crear_jugador, desarrollada en el paso anterior.

let jugador1 = crear_jugador(String::from("LeBron James"),
                                 String::from("Alero"),
                                 String::from("Los Angeles Lakers"),
                                 60, 34.7, 25.0);

let jugador2 = crear_jugador(String::from("Stephen Curry"),
                                 String::from("Base"),
                                 String::from("Golden State Warriors"),
                                 58, 33.2, 29.5);

Estos jugadores los vamos a agregar a un arreglo de jugadores, recordemos un arreglo representa colección de elementos del mismo tipo (en este caso instancias de jugador) con una longitud fija (2 para este caso). Se define utilizando corchetes y especificando el tipo de los elementos y el número de elementos que contendrá el arreglo.

let arreglo_jugadores: [JugadorNBA; 2] = [jugador1, jugador2];

Sin embargo, la manera más simple se puede definir es:

let arreglo_jugadores = [jugador1, jugador2];

Para validar, podemos imprimir en pantalla los datos de este arreglo utilizando la función imprimir_jugador, construida en el paso anterior.

imprimir_jugador(&arreglo_jugadores[0]);
imprimir_jugador(&arreglo_jugadores[1]);

Guarde el archivo, utilice el comando cargo build para compilar el programa y el comando cargo run para ejecutarlo. Si el código es correcto, debería ver la información detallada de los dos jugadores.

cargo build
cargo run

Si todo es correcto, su programa debería ser similar al presentado a continuación:

struct JugadorNBA {
    nombre: String,
    posicion: String,
    equipo: String,    
    partidos_jugados: i8,
    minutos_por_juego: f32,
    puntos_por_juego: f32,
}

fn crear_jugador(nombre: String, posicion: String, equipo: String, partidos_jugados: i8, 
                 minutos_por_juego: f32, puntos_por_juego: f32) -> JugadorNBA {
    JugadorNBA {
        nombre,
        posicion,
        equipo,    
        partidos_jugados,
        minutos_por_juego,
        puntos_por_juego,
    }
}

fn imprimir_jugador(jugador: &JugadorNBA) {
    println!("\n*** Información del jugador ***");
    println!("Nombre: {}", jugador.nombre);
    println!("Equipo: {}", jugador.equipo);
    println!("Posición: {}", jugador.posicion);    
    println!("Total partidos jugados: {}", jugador.partidos_jugados);
    println!("Promedio de minutos por partido: {}", jugador.minutos_por_juego);
    println!("Promedio de puntos por partido: {}", jugador.puntos_por_juego);
}

fn main() {    
    // Crear dos instancias de JugadorNBA
    let jugador1 = crear_jugador(String::from("LeBron James"),
                                 String::from("Alero"),
                                 String::from("Los Angeles Lakers"),
                                 60, 34.7, 25.0);

    let jugador2 = crear_jugador(String::from("Stephen Curry"),
                                 String::from("Base"),
                                 String::from("Golden State Warriors"),
                                 58, 33.2, 29.5);

    // Crear un arreglo de Jugadores
    let arreglo_jugadores = [jugador1, jugador2];

    // Acceder a los jugadores del arreglo
    imprimir_jugador(&arreglo_jugadores[0]);
    imprimir_jugador(&arreglo_jugadores[1]);
}

Paso 4 - Separar el programa en varios archivos .rs

Primero vamos a crear tres jugadores adicionales, utilice el siguiente código.

let jugador3 = crear_jugador(String::from("Kevin Durant"),
                                            String::from("Alero"),
                                            String::from("Brooklyn Nets"),
                                            45,35.6, 27.3);

let jugador4 = crear_jugador(String::from("Giannis Antetokounmpo"),
                                            String::from("Alero"),
                                            String::from("Milwaukee Bucks"),
                                            62, 36.1, 28.5);

let jugador5 = crear_jugador(String::from("Luka Dončić"),
                                            String::from("Base"),
                                            String::from("Dallas Mavericks"),
                                            56, 35.2, 26.9);

Agregue estos tres jugadores al arreglo arreglo_jugadores y si es de su preferencia, invoque la función imprimir_jugador para validar y visualizar los datos de estos nuevos jugadores en pantalla al ejecutar el programa.

let arreglo_jugadores = [jugador1, jugador2, jugador3, jugador4, jugador5];

// Acceder a los jugadores del arreglo
imprimir_jugador(&arreglo_jugadores[0]);
imprimir_jugador(&arreglo_jugadores[1]);
imprimir_jugador(&arreglo_jugadores[2]);
imprimir_jugador(&arreglo_jugadores[3]);
imprimir_jugador(&arreglo_jugadores[4]);

Como puede observar el código en el archivo main.rs se hace extenso. Por lo que sería conveniente dividir en varios archivos fuente el código del programa. Para esto vamos a crear en la carpeta src un archivo llamado jugador.rs en este archivo colocaremos la definición de la estructura JugadorNBA y las funciones crear e imprimir jugador.

El archivo jugador.rs:

struct JugadorNBA {
    nombre: String,
    posicion: String,
    equipo: String,    
    partidos_jugados: i8,
    minutos_por_juego: f32,
    puntos_por_juego: f32,
}

fn crear_jugador(nombre: String, posicion: String, equipo: String, partidos_jugados: i8, 
                 minutos_por_juego: f32, puntos_por_juego: f32) -> JugadorNBA {
    JugadorNBA {
        nombre,
        posicion,
        equipo,    
        partidos_jugados,
        minutos_por_juego,
        puntos_por_juego,
    }
}

fn imprimir_jugador(jugador: &JugadorNBA) {
    println!("\n*** Información del jugador ***");
    println!("Nombre: {}", jugador.nombre);
    println!("Equipo: {}", jugador.equipo);
    println!("Posición: {}", jugador.posicion);    
    println!("Total partidos jugados: {}", jugador.partidos_jugados);
    println!("Promedio de minutos por partido: {}", jugador.minutos_por_juego);
    println!("Promedio de puntos por partido: {}", jugador.puntos_por_juego);
}

El archivo main.rs:

// Importar el módulo jugador.rs
mod jugador; 

// Importar los elementos necesarios del módulo jugador.rs
use jugador::{JugadorNBA, crear_jugador, imprimir_jugador}; 

fn main() {    
    let jugador1 = crear_jugador(String::from("LeBron James"),
                                            String::from("Alero"),
                                            String::from("Los Angeles Lakers"),
                                            60, 34.7, 25.0);

    let jugador2 = crear_jugador(String::from("Stephen Curry"),
                                            String::from("Base"),
                                            String::from("Golden State Warriors"),
                                            58, 33.2, 29.5);

    let jugador3 = crear_jugador(String::from("Kevin Durant"),
                                            String::from("Alero"),
                                            String::from("Brooklyn Nets"),
                                            45,35.6, 27.3);

    let jugador4 = crear_jugador(String::from("Giannis Antetokounmpo"),
                                            String::from("Alero"),
                                            String::from("Milwaukee Bucks"),
                                            62, 36.1, 28.5);

    let jugador5 = crear_jugador(String::from("Luka Dončić"),
                                            String::from("Base"),
                                            String::from("Dallas Mavericks"),
                                            56, 35.2, 26.9);        

    // Crear un arreglo de JugadorNBA
    let arreglo_jugadores = [jugador1, jugador2, jugador3, jugador4, jugador5];

    // Acceder a los jugadores del arreglo
    imprimir_jugador(&arreglo_jugadores[0]);
    imprimir_jugador(&arreglo_jugadores[1]);
    imprimir_jugador(&arreglo_jugadores[2]);
    imprimir_jugador(&arreglo_jugadores[3]);
    imprimir_jugador(&arreglo_jugadores[4]);
}

En el archivo main.rs, se ha importado el módulo jugador.rs con la instrucción mod jugador; y luego indicamos que elementos son necesarios del módulo son necesarios para este programa utilizando la instrucción use jugador::{JugadorNBA, crear_jugador, imprimir_jugador};.

De esta manera, el código está más organizado y modular, separando la lógica relacionada con los jugadores en su propio archivo fuente.

En este punto, si intenta utilizar cargo para compilar y ejecutar el programa, se dará cuenta de que genera un error.

cargo build
cargo run

De hecho, el editor (Visual Studio Code) indica con más detalle el problema del código.

En Rust, los elementos dentro de un módulo son privados por defecto. Esto significa que si no se especifica explícitamente la visibilidad de un elemento (estructura, función, etc.) dentro del módulo, se considerará privado y no será accesible desde fuera del módulo, en este caso los elementos de jugador.rs son privados y no son visibles desde main.rs.

Para hacer que un elemento sea público y accesible desde fuera del módulo, se utiliza la macro pub antes de su declaración. Al agregar pub, el elemento se vuelve público. La macro pub se utiliza en Rust para indicar que un elemento (estructura, función, variable, etc.) es público y puede ser accedido desde fuera del módulo en el que se encuentra definido.

En el contexto del archivo jugador.rs, se utiliza la macro pub antes de la definición de la estructura JugadorNBA, así como de las funciones crear_jugador e imprimir_jugador. Esto se hace para que estos elementos sean visibles y accesibles desde el archivo main.rs.

Es importante destacar que el uso de la macro pub debe hacerse de forma cuidadosa, ya que la visibilidad de los elementos debe ser controlada y ajustada según las necesidades de diseño y encapsulamiento del programa.

De esta manera, es necesario ajustar el archivo jugador.rs:

pub struct JugadorNBA {
    nombre: String,
    posicion: String,
    equipo: String,    
    partidos_jugados: i8,
    minutos_por_juego: f32,
    puntos_por_juego: f32,
}

pub fn crear_jugador(nombre: String, posicion: String, equipo: String, partidos_jugados: i8, 
                 minutos_por_juego: f32, puntos_por_juego: f32) -> JugadorNBA {
    JugadorNBA {
        nombre,
        posicion,
        equipo,    
        partidos_jugados,
        minutos_por_juego,
        puntos_por_juego,
    }
}

pub fn imprimir_jugador(jugador: &JugadorNBA) {
    println!("\n*** Información del jugador ***");
    println!("Nombre: {}", jugador.nombre);
    println!("Equipo: {}", jugador.equipo);
    println!("Posición: {}", jugador.posicion);    
    println!("Total partidos jugados: {}", jugador.partidos_jugados);
    println!("Promedio de minutos por partido: {}", jugador.minutos_por_juego);
    println!("Promedio de puntos por partido: {}", jugador.puntos_por_juego);
}

Utilice cargo para compilar y ejecutar el programa, se dará cuenta de que el programa funciona correctamente.

cargo build
cargo run

Paso 5 - Algunas búsquedas sobre el arreglo de jugadores

En primer lugar, vamos a implementar la función buscar_jugador, esta función recibe por parámetro el arreglo de jugadores y una cadena con el nombre del jugador a buscar. No vamos a complicar la búsqueda, el nombre a buscar debe ser idéntico al nombre almacenado.

La función debe ser implementada en el archivo jugador.rs, y debe especificarse en la instrucción use del archivo main.rs que la nueva función debe ser incluida use jugador::{JugadorNBA, crear_jugador, imprimir_jugador, buscar_jugador}; .

pub fn buscar_jugador<'a>(jugadores: &'a [JugadorNBA], nombre_buscar: &str) -> Option<&'a JugadorNBA> {
    for jugador in jugadores {
        if jugador.nombre == nombre_buscar {
            return Some(jugador);
        }
    }
    None
}

Esta función utiliza un ciclo for para iterar sobre el arreglo de jugadores. En cada iteración, verifica si el nombre del jugador coincide con el nombre que se está buscando. Si se encuentra una coincidencia, se retorna el jugador utilizando Some(jugador). Si no se encuentra ningún jugador con el nombre buscado, se retorna None.

Esto parece simple respecto a cualquier otro lenguaje de programación. Sin embargo, si observamos con detalle aparecen elementos como Some y None, y otros elementos de la sintaxis de Rust que no hemos discutido. Tratemos de analizarlos.

En Rust, Some y None son dos variantes del tipo de dato Option<T>, que se utilizan para representar la posibilidad de que un valor esté presente (Some) o ausente (None).

En Rust, Option<T> es un tipo de dato genérico que se utiliza para representar la posibilidad de que un valor esté presente o ausente. Proporciona una forma segura y controlada de manejar situaciones en las que un valor puede o no existir.

Some y None son útiles en situaciones en las que puede haber un resultado opcional o incierto, como cuando se realiza una búsqueda y no se encuentra un resultado, o cuando se trabaja con operaciones que pueden fallar. Permiten manejar de forma segura los casos en los que un valor puede o no estar presente, evitando posibles errores o comportamientos inesperados.

Al utilizar Option<T>, se fomenta la programación segura y robusta al requerir una verificación explícita para manejar ambos casos: cuando hay un valor presente (Some) y cuando no lo hay (None). Esto ayuda a evitar errores como el acceso a valores nulos o indefinidos.

Como puede observar el retorno de la función, utiliza **Option<T>**por lo antes mencionado. En este caso, la función retorna un Option<&'a JugadorNBA>, lo que significa que puede devolver un Some con una referencia a un JugadorNBA válido o un None si no se encuentra el jugador.

Finalmente, en la signatura de la función buscar_jugador<'a>(jugadores: &'a [JugadorNBA], nombre_buscar: &str), se coloca <'a> antes del nombre de la función para indicar que existe un parámetro de tiempo de vida llamado 'a. Además, se utiliza 'a en la definición de los tipos de referencia, en los parámetros y en el tipo de retorno.

El uso de parámetros de tiempo de vida en Rust permite rastrear y garantizar que las referencias sean válidas durante un cierto período de tiempo. Al colocar <'a> en la firma de la función, se especifica que hay un tiempo de vida llamado 'a asociado con las referencias que se utilizan en los parámetros y el tipo de retorno.

De no especificar un parámetro de tiempo de vida recibiríamos el error "expected named lifetime parameter", esto significa que el compilador de Rust esperaba que se nombrara el parámetro de tiempo de vida 'a en lugar de ser anónimo.

En resumen, el parámetro de tiempo de vida 'a en la función buscar_jugador se utiliza para garantizar la validez de las referencias en los parámetros y el tipo de retorno.

Nota: Lo sé, el lifetime parameter es complejo de entender. Aún no tengo una comprensión completa al respecto, pero espero profundizar en el tema y lograr clarificar este texto en un futuro cercano.

En Rust, el tiempo de vida (lifetime) es una característica del lenguaje que se utiliza para garantizar la validez de las referencias en tiempo de compilación y prevenir problemas de acceso a memoria inválida o datos que ya no existen.

El tiempo de vida se refiere al período de tiempo durante el cual una referencia es válida y puede ser utilizada de manera segura. Se utiliza para rastrear la relación entre las referencias y los objetos a los que hacen referencia, asegurándose de que una referencia no supere la vida útil de los datos a los que se refiere.

El tiempo de vida se representa utilizando una notación de apóstrofe ('a, 'b, etc.) y se especifica en la firma de una función, estructura o impl bloque. Esto permite al compilador determinar las dependencias entre las referencias y los objetos referenciados, y así garantizar que no se produzcan referencias inválidas o data races.

Al utilizar el sistema de tiempo de vida en Rust, se logra un manejo seguro y eficiente de las referencias y se evitan errores comunes como el acceso a referencias nulas o la liberación de memoria prematura. Además, el sistema de tiempo de vida permite al compilador realizar verificaciones estáticas en tiempo de compilación para garantizar la correcta gestión de la memoria sin necesidad de recolección de basura.

Paso 6 - Invocar a la función buscar_jugador

En la función main(), agregue el siguiente bloque de código:

// Buscar un jugador por nombre
let nombre_buscar = String::from("Stephen Curry");
let jugador_encontrado = buscar_jugador(&arreglo_jugadores, &nombre_buscar);

if let Some(jugador) = jugador_encontrado {
    println!("Jugador encontrado:");
    imprimir_jugador(jugador);
} else {
    println!("No se encontró ningún jugador con ese nombre.");
}

Incluso en lugar de un condicional, podemos utilizar la macro match:

    // Buscar un jugador por nombre
let nombre_buscar = String::from("Stephen Curry");
let jugador_encontrado = buscar_jugador(&arreglo_jugadores, &nombre_buscar);

match jugador_encontrado {
        Some(jugador) => {
            println!("Jugador encontrado:");
            imprimir_jugador(jugador);
    },
    None => println!("No se encontró ningún jugador con ese nombre."),
}

La función buscar_jugador retorna Some(jugador) si se encuentra un jugador con el nombre buscado, y None si no se encuentra ningún jugador. Luego, se utiliza una declaración if/else o match para manejar ambos casos. Si se encuentra un jugador, se imprime su información utilizando la función imprimir_jugador. Si no se encuentra ningún jugador, se imprime un mensaje indicando que no se encontró ningún jugador con ese nombre.

Utilice cargo para compilar y ejecutar el programa, se dará cuenta de que el programa funciona correctamente.

cargo build
cargo run

Paso 7 - Buscar al jugador con mayor promedio de puntos

Implemente la función buscar_jugador_mas_puntos en el archivo jugador.rs, esta función toma como parámetro un arreglo de jugadores jugadores y devuelve una opción Option<&JugadorNBA>.

Al finalizar el ciclo, se devuelve jugador_mas_puntos, que contendrá el jugador con más puntos por juego si se encontró alguno, o None si no se encontró ningún jugador.

pub fn buscar_jugador_mas_puntos(jugadores: &[JugadorNBA]) -> Option<&JugadorNBA> {
    let mut max_puntos_por_juego = 0.0;
    let mut jugador_mas_puntos: Option<&JugadorNBA> = None;

    for jugador in jugadores {
        if jugador.puntos_por_juego > max_puntos_por_juego {
            max_puntos_por_juego = jugador.puntos_por_juego;
            jugador_mas_puntos = Some(jugador);
        }
    }

    jugador_mas_puntos
}

Dentro de la función, se inicializan dos variables: max_puntos_por_juego se establece en 0.0 como el valor máximo inicial, y jugador_mas_puntos se establece en None para indicar que no se ha encontrado ningún jugador aún.

Luego, en la función main() se llama a la función buscar_jugador_mas_puntos pasando el arreglo de jugadores como argumento. Si la función encuentra un jugador con más puntos por juego, se imprime su información utilizando la función imprimir_jugador. Si no se encuentra ningún jugador, se imprime un mensaje indicando que no se encontró ningún jugador.

if let Some(jugador) = buscar_jugador_mas_puntos(&arreglo_jugadores) {
        println!("Jugador con más puntos por juego:");
        imprimir_jugador(jugador);
    } else {
        println!("No se encontró ningún jugador.");
    }

Recuerde debe especificarse en la instrucción use del archivo main.rs que debe ser incluida use jugador::{JugadorNBA, crear_jugador, imprimir_jugador, buscar_jugador, buscar_jugador_mas_puntos};

Utilice cargo para compilar y ejecutar el programa, se dará cuenta de que el programa funciona correctamente.

cargo build
cargo run

Paso 8 - Ordenar la función main()

Para darle un poco de orden a la función main(), vamos a utilizar la macro match para construir un menú de navegación para nuestra aplicación. La función main() debería quedarnos de la siguiente manera.

fn main() { 
    let jugador1 = crear_jugador(String::from("LeBron James"),
    String::from("Alero"),
    String::from("Los Angeles Lakers"),
    60, 34.7, 25.0);

    let jugador2 = crear_jugador(String::from("Stephen Curry"),
    String::from("Base"),
    String::from("Golden State Warriors"),
    58, 33.2, 29.5);

    let jugador3 = crear_jugador(String::from("Kevin Durant"),
    String::from("Alero"),
    String::from("Brooklyn Nets"),
    45,35.6, 27.3);

    let jugador4 = crear_jugador(String::from("Giannis Antetokounmpo"),
    String::from("Alero"),
    String::from("Milwaukee Bucks"),
    62, 36.1, 28.5);

    let jugador5 = crear_jugador(String::from("Luka Dončić"),
    String::from("Base"),
    String::from("Dallas Mavericks"),
    56, 35.2, 26.9);        

    // Crear un arreglo de JugadorNBA
    let arreglo_jugadores = [jugador1, jugador2, jugador3, jugador4, jugador5];

    loop {
        println!("\n\nMenú de la aplicación");
        println!("---------------------");
        println!("1. Listar todos los jugadores");
        println!("2. Buscar jugador por nombre");
        println!("3. Jugador con mejor promedio de puntos");
        println!("4. Salir");
        println!("Ingrese una opción:");

        let mut opcion = String::new();
        io::stdin()
            .read_line(&mut opcion)
            .expect("Error al leer la entrada");

        match opcion.trim() {
            "1" => {                
                println!("\n-> Jugadores registrados en la aplicación");
                for jugador in &arreglo_jugadores {
                    imprimir_jugador(&jugador);
                }
            }
            "2" => {                
                println!("\n-> Buscar jugador por nombre");                
                let nombre_buscar = String::from("Giannis Antetokounmpo");         
                let jugador_encontrado = buscar_jugador(&arreglo_jugadores, &nombre_buscar.trim());

                match jugador_encontrado {
                    Some(jugador) => {
                    println!("\n*** Jugador encontrado: ***");
                    imprimir_jugador(jugador);
                },
                None => println!("\n -> No se encontró ningún jugador con ese nombre."),
                }
            }
            "3" => {
                if let Some(jugador) = buscar_jugador_mas_puntos(&arreglo_jugadores) {
                    println!("\n-> Jugador con más puntos por juego:");
                    imprimir_jugador(jugador);
                } else {
                    println!("\n -> No se encontró ningún jugador.");
                }
            }
            "4" => {
                println!("*** Saliendo de la aplicación *** ");
                break;
            }
            _ => {
                println!("-> Opción inválida. Por favor, ingrese una opción válida.");
            }
        }
    }
}

Se utiliza un ciclo loop para mantener el menú de la aplicación en funcionamiento. El usuario puede ingresar una opción seleccionando un número del 1 al 4.

Dentro del bloque match, se realiza el emparejamiento de la opción ingresada con las diferentes funciones a invocar. Si el usuario selecciona la opción 1, se ejecutará la lógica para listar todos los jugadores, se ejecuta un for que recorre el arreglo de jugadores e invoca la función imprimir jugador para colocar sus datos en pantalla.

Si selecciona la opción 2, se ejecutará la lógica para buscar un jugador por nombre. En este caso dejamos fijo el nombre del jugador a manera de ejemplo, pero podemos utilizar la entrada estándar para solicitar un String al usuario.

Si selecciona la opción 3, invoca la función buscar_jugador_mas_puntos e imprime en pantalla la información con el jugador con mejor promedio de puntos.

La opción 4 rompe la función loop utilizando la macro break, y si el usuario ingresa una opción diferente de 1, 2, 3 o 4, se ejecutará la rama _ que imprime un mensaje de opción inválida.

Este código obtiene un dato de entrada del usuario y luego imprime un mensaje como salida. No olvide importar la biblioteca de entrada/salida “io” al ámbito utilizando la palabra reservada use.

use std::io;

Utilice cargo para compilar y ejecutar el programa, se dará cuenta de que el programa funciona correctamente.

cargo build
cargo run

Repositorio

Enlace al repositorio del proyecto en Gitlab por si desea descargar el código completo:

Did you find this article valuable?

Support Jesse Padilla by becoming a sponsor. Any amount is appreciated!