Rusty Full Stack

El blog para los amantes de Rust, Ionic y Vuejs

Jaime Blandón
Jaime Blandón Desarrollador de software desde 2009, entusiasta de Rust, Vuejs y Ionic!. Fundador de este blog, espero que las publicaciones te sean de utilidad y si tienes comentarios para mejorar, son bienvenidos.

Organizar Código En Rust - Módulos

Este es el "segundo capítulo" de nuestra mini-serie sobre cómo podemos organizar nuestro código en Rust, si quieres leer nuestro primer capítulo, puedes hacerlo en el siguiente enlace:

Organizar Código en Rust - Paquetes y Librerías.

¿Qué es un módulo en Rust?

Los módulos en Rust no son más que agrupaciones que podemos de funcionalidades que podemos hacer en nuestro código, con la ventaja que nos permite definir un "alcance" sobre que partes de nuestro código pueden accederse de forma pública (por otras funciones o estructuras) y cuales son de carácter privado (solamente puede accederse desde el módulo mismo).

Si el concepto anterior de ha parecido un poco confuso, no te vayas a desanimar, lo vamos a entender mejor haciendo un pequeño ejemplo 🤓, sin embargo, antes vamos a hablar de los pasos que realiza Rust para poder encontrar un módulo en nuestro código, nuevamente te comento que esta mini-serie está basada en el séptimo capítulo del libro oficial de Rust "Managing Growing Projects with Packages, Crates, and Modules", si tienes la oportunidad, te recomiendo darle una leída.

Las reglas que Rust sigue para encontrar módulos dentro de nuestro paquete, crate o proyecto son:

  1. El primer lugar donde el compilador busca un módulo es en el root de nuestro crate o proyecto, este puede ser la carpeta src/lib.rs o src/main.rs
  2. La segunda regla para que Rust pueda encontrar nuestro módulo, es que ellos deben declararse utilizando la palabra reservada mod y lo busca en nuestro proyecto siguiente estas rutas, Primero en la raíz de nuestro crate o proyecto, segundo en un archivo en src/nombre_del_modulo.rs y por último en un archivo en src/nombre_del_modulo/mod.rs.
  3. La declaración de sub módulos, también se realizar utilizando la palabra reservada mod y la búsqueda de los sub módulos es. Primero en la raíz de nuestro móduloSegundo en un archivo en src / nombre_del_modulo/ nombre_submodulo.rs y por último en un archivo en src / nombre_del_modulo/ nombre_submodulo/ mod.rs.
  4. Una vez un módulo ha sido agregado a nuestro crate, podemos acceder a sus componentes utilizando la palabra reservada use y el path de nuestro crate, por ejemplo: use crate::nombre_del_modulo::alguna_funcion
  5. Para declarar que un módulo es privado se usa solamente la palabra reservada mod, si queremos hacerlo público o accesible desde otros puntos lo hacemos declarándolo como pub mod.

Ok, sé que a lo mejor son muchas reglas para aprenderse 🥵, pero verás que todo es bastante sencillo cuando hagamos un pequeño ejemplo.

Ejemplo de Módulos en Rust.

Puedes ver el código terminado del ejemplo en el repositorio en github dando click a este enlace.

En este ejemplo vamos a crear un programa en Rust utilizando módulos, nuestro programa tratará de simular una caja de supermercado y tendrá las siguientes funcionalidades:

  • Cada ejecución del programa será el equivalente a una compra (Ok a lo mejor no sea la caja de supermercado más eficiente del mundo, recuerda que es solamente para aprender que son los módulos, no te vayas a enojar 🥺).
  • Por cada compra podremos ir agregando items los cuales los vamos a representar en una estructura que tendrá tres atributos: nombre item, cantidad y precio unitario, todo se registrará manualmente (sí, sé que no es eficiente, pero recuerda es solamente un ejemplo 😇).
  • Podremos ver los items de la compra y también remover los que hemos ido registrando.
  • Al final vamos a poder mostrar el total de la compra y simularemos los pagos, vamos a permitir que el cliente pueda pagar su compra en efectivo, con tarjeta de crédito o por transferencia bancaria directa. Los pagos serán simulados por su puesto 😬
  • Por facilidad, no realizaremos validaciones (en un ambiente laboral SI debes hacerlas)

Hay muchas formas de poder hacer el programa, pero para nuestro ejemplo vamos a crear dos módulos.

  • El módulo de registro de la compra.
  • El módulo de pago

Empecemos creando nuestro paquete, en cualquier carpeta de tu elección ejecuta los siguientes comandos.

cargo new caja-supermercado

cd caja-supermercado

Podríamos crear un nuevo crate o dividir cada módulo en crates por separado, pero esta vez me gustaría mostrarte la creación de los módulos desde el crate root, es decir desde src/lib.rs, ya en el siguiente post hablaremos como dividir nuestros módulos en varios archivos, pero por el momento empezaremos por lo básico.

Dentro la carpeta caja-supermercado/src, creemos un archivo en blanco y le llamaremos lib.rs, nuestro paquete debería de verse así:

Ahora creemos el primer módulo dentro de src/lib.rs, recuerda que nuestros módulos debemos de definirlos utilizando la palabra reservada mod.

        
        
            // src/lib.rs

mod compra {

}

        
        
    

Con el bloque de código anterior acabamos de definir un módulo llamado "compra", en este módulo vamos a crear las funciones para agregar o quitar un item, también para ver los items y el total de compra, agreguemos las definiciones que vamos a utilizar, empecemos definiendo la estructura "Item" que habíamos mencionado anteriormente y las funciones.

        
        
            // src/lib.rs

mod compra {

    #[derive(Debug)]
    struct Item {
        nombre: String, // Nombre del item
        precio_unitario: f32, // Precio Unitario del item
        cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agregara un item a un vector con todos los items de la compra
    }

    fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
    }

    fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
    }

    fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el total a pagar de todos los items de la compra
        0.0
    }
}


        
        
    

En el bloque de código anterior, estamos creando un módulo al que hemos llamado "compra" y le hemos colocado la definición de las funciones para el manejo de los items de la caja.

Ahora vamos a agregarle el código de cada una de las funciones, no he colocado mucho comentario porque no es el objetivo de este post, el objetivo es que veamos como funcionan los módulos.

        
        
            // src/lib.rs

mod compra {

    #[derive(Debug)]
    struct Item {
        nombre: String, // Nombre del item
        precio_unitario: f32, // Precio Unitario del item
        cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }
        
        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }
}

        
        
    

Con lo anterior hemos creado el modulo de compra y sus funciones internas, ahora intentemos agregarlo en nuestro src/main.rs, por el momento solamente intentemos utilizar el módulo con la palabra reservada use e intentemos ejecutarlo. Es importante comentar que el nombre del crate en este caso es el nombre de nuestro paquete.

        
        
            // src/main.rs

use caja_supermercado::compra;

fn main() {
    println!("Usando el modulo");
}

        
        
    

Si ahora intentamos compilar nuestro programa vamos a ver que el compilador terminará mostrándonos un error, para compilar el programa ejecutemos en una terminal (dentro de la carpeta caja-supermercado)

cargo build

Dentro del error encontrarás el siguiente mensaje:

Este mensaje quiere decir que desde nuestro crate root (src/main.rs), no es posible utilizar ninguna función del módulo, esto es debido a que ha sido declarado de forma privada, para poder utilizarlo fuera de src/lib.rs, debemos definirlo de forma pública, agreguemos la palabra reservada pub antes de mod, de la siguiente manera:

        
        
            // src/lib.rs

pub mod compra {

    #[derive(Debug)]
    struct Item {
        nombre: String, // Nombre del item
        precio_unitario: f32, // Precio Unitario del item
        cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }
        
        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }
}

        
        
    

Si ahora compilamos nuestro programa, veremos muchos warnings que iremos resolviendo poco a poco, pero lo importante es que nuestro programa compilara sin problemas:

cargo build

Genial, ahora que ha compilado modifiquemos un poco nuestro src/main.rs para intentar utilizar una de las funciones del módulo, en este caso hagamos una modificación simple para declarar un vector el cual contendrá todos los items de la compra.

        
        
            // src/main.rs

// Indiquemos al modulo que queremos utilizar la estructura Item definida en el modulo
use caja_supermercado::compra::Item;

fn main() {
    let mut items_compra: Vec<Item> = Vec::new();
}

        
        
    

Si ahora intentamos compilar nuestro programa con:

cargo build

Veremos un nuevo error:

Pero si habíamos declarado nuestro módulo de manera pública ¿por qué nos ha lanzado el error 😣?, acá entra en juego lo que habíamos comentado anteriormente sobre los alcances de un módulo, aunque  le hayamos indicado a Rust que queremos hacer el módulo público, es necesario indicar explícitamente qué funciones, estructuras, etc, queremos que otras partes del código también tengan acceso público, al analizar lo que queremos que haga el módulo, podemos constatar que queremos que tanto la estructura, sus atributos y demás funciones sean públicas por lo que haremos que cada una de ellas pueda ser utilizada externamente de esta forma en src/lib.rs

        
        
            // src/lib.rs

pub mod compra {

    #[derive(Debug)]
    pub struct Item {
        pub nombre: String, // Nombre del item
        pub precio_unitario: f32, // Precio Unitario del item
        pub cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    pub fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    pub fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    pub fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    pub fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }

        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }
}


        
        
    

Si ahora compilamos nuestro programa, veremos siempre algunos warning que ya resolveremos, pero lo importante es que nuestro programa ahora sí logra compilar.

cargo build

Perfecto! ahora vamos a hacer nuestra caja un poco más interactiva, primero vamos a decirle a Rust que vamos a usar todas las funciones del módulo de compra, para ello podemos utilizar use caja_supermercado::*, o individualmente como será nuestro caso dentro de src/main.rs, para que veas la sintáxis para importar más de una función o estructura (colocándolo dentro de {}).

        
        
            // src/main.rs

use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra};

fn main() {
    let mut items_compra: Vec<Item> = Vec::new();
}

        
        
    

Si lo compilas verás que aunque existen warnings, nuestro programa sigue compilando bien. Ahora si agreguemos un menu y su interacción, esto lo podríamos hacer dentro de otro módulo, pero por el momento vamos a crear las funciones dentro de src/main.rs.

        
        
            // src/main.rs

use std::io::stdin;
use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra};

fn mostrar_menu() {
    println!("OPCIONES:");
    println!("1. Agregar Item");
    println!("2. Quitar Item");
    println!("3. Mostrar Items");
    println!("4. Total a Pagar");
    println!("5. Realizar Pago");
    println!("6. Cancelar Compra y Salir");
    println!("Selecciona una opcion");
}

fn manejar_agregar_item(items_compra: &mut Vec<Item>) {
    println!("En Construccion");
}

fn manejar_quitar_item(items_compra: &mut Vec<Item>) {
    println!("En Construccion");
}

fn manejar_realizar_pago() {
    println!("En Construccion");
}

fn main() {
    // Creamos un vector para llevar el registro de los items de la compra
    let mut items_compra: Vec<Item> = Vec::new();

    // Iniciamos un loop en el cual vamos a preguntar al usuario la accion a realizar
    // Dependiendo de sus seeleccion vamos a realizar una tarea, todas ellas dependeran
    // de funciones dentro del modulo "compra"
    loop {
        mostrar_menu();

        // Obtenemos la opcion que selecciona el usuario
        let mut opcion: String = String::new();
        stdin().read_line(&mut opcion).unwrap();
        // limpiando el input de la terminal
        let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

        match opcion_seleccionada {
            1 => manejar_agregar_item(&mut items_compra), // Agregar un item
            2 => manejar_quitar_item(&mut items_compra), // quitar un item
            3 => mostrar_items(&items_compra), // mostrar todos los items y sus indices
            4 => println!("Total a pagar: ${}", total_compra(&items_compra)), // mostrando el total a pagar
            5 => manejar_realizar_pago(), // realizar el pago
            6 => break, // terminando el programa
            _ => println!("Opcion Invalida") // la opcion no es valida, el programa continua
        };
    }

    println!("Programa Finalizado");
}

        
        
    

Si ahora ejecutamos nuestro programa con:

cargo run

Veremos algunos warnings que ya resolveremos en un momento pero lo importante es que veremos el menu de opciones que tenemos disponible, si seleccionamos alguna (a excepcion de la opcion 6, 3, 4 y 7) veremos que nos mostrará un mensaje que dice "En Construccion", y el programa continuará mostrando el menú de opciones, si seleccionamos la opción 6 el programa terminará.

Ya nuestro programa va tomando forma 😍, ahora vamos a utilizar nuestro módulo de compra para ir haciendo "la cuenta" en nuestra caja virtual. Agreguemos el siguiente código en src/main.rs para agregar y quitar items de la cuenta.

        
        
            // src/main.rs

use std::io::stdin;
use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra};

fn mostrar_menu() {
    println!("OPCIONES:");
    println!("1. Agregar Item");
    println!("2. Quitar Item");
    println!("3. Mostrar Items");
    println!("4. Total a Pagar");
    println!("5. Realizar Pago");
    println!("6. Cancelar Compra y Salir");
    println!("Selecciona una opcion");
}

fn manejar_agregar_item(items_compra: &mut Vec<Item>) {
    // Solicitando que el usuario registre los datos del item
    // por facilidad no hemos colocado validaciones, en un ambiente
    // laboral SI debes colocarlas.
    // Quiza no es el input mas optimo del planeta pero sirve a manera
    // de ejemplo de uso de modulos
    let mut input: String = String::new();
    println!("Escribe los detalles del Item");

    println!("NOMBRE:");
    stdin().read_line(&mut input).unwrap();
    let nombre = input.replace("\n", "").replace("\r", "");

    println!("CANTIDAD:");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let cantidad = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    println!("PRECIO UNITARIO: ");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let precio_unitario = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    // Creando el item con la estructura que importamos de nuestro modulo
    let item: Item = Item {
        nombre,
        precio_unitario,
        cantidad
    };

    // Agregando el item a la compra
    agregar_item(items_compra, item);

    println!("Item Agregado!");
}

fn manejar_quitar_item(items_compra: &mut Vec<Item>) {

    // mostrando los items par que el usuario pueda saber cual quitar
    // REUTILIZANDO NUESTRO MODULO !!!
    println!("Selecciona el indice que quieres quitar");
    mostrar_items(items_compra);

    // Obteniendo el itema a eliminar
    let mut input: String = String::new();
    stdin().read_line(&mut input).unwrap();
    // limpiando el input de la terminal
    let indice = input.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    // Eliminando el item utilizando la funcion dentro del modulo de compra
    quitar_item(items_compra, indice);
    println!("Item eliminado");
}

fn manejar_realizar_pago() {
    println!("En Construccion");
}

fn main() {
    // Creamos un vector para llevar el registro de los items de la compra
    let mut items_compra: Vec<Item> = Vec::new();

    // Iniciamos un loop en el cual vamos a preguntar al usuario la accion a realizar
    // Dependiendo de sus seeleccion vamos a realizar una tarea, todas ellas dependeran
    // de funciones dentro del modulo "compra"
    loop {
        mostrar_menu();

        // Obtenemos la opcion que selecciona el usuario
        let mut opcion: String = String::new();
        stdin().read_line(&mut opcion).unwrap();
        // limpiando el input de la terminal
        let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

        match opcion_seleccionada {
            1 => manejar_agregar_item(&mut items_compra), // Agregar un item
            2 => manejar_quitar_item(&mut items_compra), // quitar un item
            3 => mostrar_items(&items_compra), // mostrar todos los items y sus indices
            4 => println!("Total a pagar: ${}", total_compra(&items_compra)), // mostrando el total a pagar
            5 => manejar_realizar_pago(), // realizar el pago
            6 => break, // terminando el programa
            _ => println!("Opcion Invalida") // la opcion no es valida, el programa continua
        };
    }

    println!("Programa Finalizado");
}

        
        
    

Si ahora ejecutamos nuestro programa veremos que ya no hay warnings y que somos capaces de agregar items (opcion 1) y mostrarlos (opcion 3) 😎

También podemos quitar items (recuerda que no hemos colocado validaciones, por lo que asegúrate que existan items, en un ambiente laborar RECUERDA colocarlas)

Incluso también muestra el total a pagar 🤑 !!, en la siguiente imagen he agregado algunos items para mostrar algún total.

Ahora lo único que nos queda por hacer es agregar el módulo de pago (el cual lo simularemos), te había mencionado que los alcances en los módulos también pueden ser privados, un módulo de pago podría ser un buen candidato a contener algunas funciones de las cuales no queremos que código externo tenga acceso a ello por seguridad, por ejemplo los métodos de transferencia bancaria o pago con tarjeta porque pueden contener acceso a servicios externos bastante críticos en temas de seguridad, por lo que vamos a abstraer un poco el funcionamiento del pago y manejarlo de una forma más segura.

Creando Módulos con Funciones Privadas en Rust.

Anteriormente habíamos mencionado que nuestro módulo de pagos permitirá al usuario realizar el pago de tres formas:

  • Pago en efectivo, el cliente brinda un monto y la función dirá si el pago fue efectuado y el cambio a devolverle al cliente.
  • Pago con tarjeta, el cliente paga con su tarjeta, la función dirá si tuvo éxito (simulado) y el cambio será de $0.0
  • Pago por transferencia bancaria, la función dira si tuvo éxito (simulado) y el cambio será de $0.0

Agreguemos el módulo siempre dentro de src/lib.rs, por facilidad de lectura lo pondremos antes del modulo de "compra", en realidad el orden en este caso no es tan importante, también por el momento dejaremos todo en un contexto privado.

Vamos a crear un enum, el cual nos permitirá indicarle a nuestro programa el método de pago elegido por el cliente y una función general de pago.

        
        
            // src/lib.rs

mod pago {

    enum MetodoDePago {
        Efectivo,
        Tarjeta,
        TransferenciaBancaria,
    }

    struct ResultadoPago {
        metodo_pago: String, // La descripcion del metodo de pago
        fue_exitoso: bool, // true si el pago se pudo hacer o false si no se pudo hacer
        cambio: f32, // Cambio a devolver al cliente.
    }

    fn pagar(metodo_de_pago: MetodoDePago, monto_a_pagar: f32, recibido_del_cliente: f32, tarjeta: &str) -> ResultadoPago{
        // El parametro metodo_de_pago es la forma de pago elegida por el cliente.
        // El parametro monto_a_pagar es el total a pagar de la compra.
        // recibido_del_cliente es la cantidad de dinero recibida del cliente, si no es efectivo, es igual al monto a pagar
        // tarjeta, es el numero de tarjeta del cliente, si el pago es en efectivo o por transferencia, no es necesario, puede ser cualquiera
    }

    fn pago_en_efectivo(monto_a_pagar: f32, recibido_del_cliente: f32) -> ResultadoPago {
       // Si el pago es en efectivo, se calculara el cambio a devolver al cliente
    }

    fn pago_con_tarjeta(monto_a_pagar: f32, numero_tarjeta: &str) -> ResultadoPago {
        // Si el pago es con tarjeta, simularemos el resultado
    }

    fn pago_por_transferencia_bancaria(monto_a_pagar: f32) -> ResultadoPago {
        // Si el pago es via transferencia, simularemos que solamente necesitamos la cuenta del supermercado
        // la cual seria la cuenta a recibir el dinero y tambien simularemos el resultado de la transferencia
        // Esta cuenta supondriamos que es secreta
    }

}

pub mod compra {

    #[derive(Debug)]
    pub struct Item {
        pub nombre: String, // Nombre del item
        pub precio_unitario: f32, // Precio Unitario del item
        pub cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    pub fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    pub fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    pub fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    pub fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }

        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }
}

        
        
    

No intentes compilar el programa porque te mostrará algunos errores que vamos a corregir en un momento. Quisiera que vieras las definiciones del método de pago, por el momento todas están privadas, ahora es buen momento de detenernos un poco a pensar si queremos que todas nuestras funciones y estructuras sean públicas o queremos que algunas queden de forma privada.

  • El enum MetodoDePago, su idea es que el cajero pueda seleccionar el método de pago elegido por el cliente, como el usuario debe usarlo directamente, este enum puede ser público.
  • La struct ResultadoPago, indicará al cajero si el pago fue o no fue exitoso, por lo que también puede ser público.
  • La función pagar, es la que invocará a cada función específica de pago (efectivo, con tarjeta o transferencia), por lo que será necesaria invocarla desde nuestro archivo src/main.rs, esta puede ser pública también.
  • Las funciones pago_con_tarjeta y pago_por_transferencia_bancaria, suenan que pueden llegara a contener conexiones a servicios bancarios y que deben estar bastante protegidos, quien se encargará de invocarlas será la función pagar, que ya es parte de su mismo módulo, por lo cual podemos protegerlas colocándolas como privadas.
  • La función pago_en_efectivo, a lo mejor no es muy crítica, ya que solamente calcula un cambio, pero de igual manera será invocada por la función pagar que ya está dentro del mismo módulo, por lo que la podemos dejar en privado, para seguir las recomendaciones de solamente poner algo en público si es estrictamente necesario.

Ahora pongamos como público lo que vimos que debe ser público y privado lo que debe ser privado, también completemos cada método del módulo de pagos en src/lib.rs.

        
        
            // src/lib.rs

pub mod pago {

    pub enum MetodoDePago {
        Efectivo,
        Tarjeta,
        TransferenciaBancaria,
    }

    pub struct ResultadoPago {
        pub metodo_pago: String, // La descripcion del metodo de pago
        pub fue_exitoso: bool, // true si el pago se pudo hacer o false si no se pudo hacer
        pub cambio: f32, // Cambio a devolver al cliente.
    }

    pub fn pagar(metodo_de_pago: MetodoDePago, monto_a_pagar: f32, recibido_del_cliente: f32, tarjeta: &str) -> ResultadoPago{
        // El parametro metodo_de_pago es la forma de pago elegida por el cliente.
        // El parametro monto_a_pagar es el total a pagar de la compra.
        // recibido_del_cliente es la cantidad de dinero recibida del cliente, si no es efectivo, es igual al monto a pagar
        // tarjeta, es el numero de tarjeta del cliente, si el pago es en efectivo o por transferencia, no es necesario, puede ser cualquiera

        // Ahora, dependiendo del metodo de pago elegido por el cliente, invocamos las funciones privadas, esto puede hacerse
        // porque estan dentro del mismo alcance de este metodo.
        let resultado = match metodo_de_pago {
            MetodoDePago::Efectivo => pago_en_efectivo(monto_a_pagar, recibido_del_cliente),
            MetodoDePago::Tarjeta => pago_con_tarjeta(monto_a_pagar, tarjeta),
            MetodoDePago::TransferenciaBancaria => pago_por_transferencia_bancaria(monto_a_pagar)
        };

        resultado
    }

    fn pago_en_efectivo(monto_a_pagar: f32, recibido_del_cliente: f32) -> ResultadoPago {
       // Si el pago es en efectivo, se calculara el cambio a devolver al cliente
        ResultadoPago {
            metodo_pago: String::from("En Efectivo"),
            fue_exitoso: true,
            cambio: recibido_del_cliente - monto_a_pagar
        }
    }

    fn pago_con_tarjeta(monto_a_pagar: f32, numero_tarjeta: &str) -> ResultadoPago {
        // Si el pago es con tarjeta, simularemos el resultado

        // Aca se estaria procesando todo aquello critico a nivel de seguridad.
        println!("Realizando el pago con el servicio de tarjeta credito/debito");
        println!("Monto a pagar: {}", monto_a_pagar);
        println!("Tarjeta: {}", numero_tarjeta);

        //Ahora simulamos el resultado
        ResultadoPago {
            metodo_pago: String::from("Tarjeta"),
            fue_exitoso: true,
            cambio: 0.0
        }

    }

    fn pago_por_transferencia_bancaria(monto_a_pagar: f32) -> ResultadoPago {
        // Si el pago es via transferencia, simularemos que solamente necesitamos la cuenta del supermercado
        // la cual seria la cuenta a recibir el dinero y tambien simularemos el resultado de la transferencia
        // Esta cuenta supondriamos que es secreta

        // Aca se estaria procesando todo aquello critico a nivel de seguridad.
        println!("Realizando las conexiones y transferencias con el banco");
        println!("Monto a pagar: {}", monto_a_pagar);
        println!("Cuenta Bancaria Secreta (o no tanto jejeej): 123456789-0");

        //Ahora simulamos el resultado
        ResultadoPago {
            metodo_pago: String::from("Transferencia Bancaria"),
            fue_exitoso: true,
            cambio: 0.0
        }
    }
}

pub mod compra {

    #[derive(Debug)]
    pub struct Item {
        pub nombre: String, // Nombre del item
        pub precio_unitario: f32, // Precio Unitario del item
        pub cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    pub fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    pub fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    pub fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    pub fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }

        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }
}

        
        
    

Si compilamos con nuestro programa con:

cargo build

vemos que ahora sí compila sin problemas, pero ahora agreguemos el nuevo módulo en nuestro crate root (src/main.rs) recuerda que debemos utilizar la palabra reservada use.

        
        
            // src/main.rs

use std::io::stdin;
use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra};
use caja_supermercado::pago::{MetodoDePago, ResultadoPago, pagar};

fn mostrar_menu() {
    println!("OPCIONES:");
    println!("1. Agregar Item");
    println!("2. Quitar Item");
    println!("3. Mostrar Items");
    println!("4. Total a Pagar");
    println!("5. Realizar Pago");
    println!("6. Cancelar Compra y Salir");
    println!("Selecciona una opcion");
}

fn manejar_agregar_item(items_compra: &mut Vec<Item>) {
    // Solicitando que el usuario registre los datos del item
    // por facilidad no hemos colocado validaciones, en un ambiente
    // laboral SI debes colocarlas.
    // Quiza no es el input mas optimo del planeta pero sirve a manera
    // de ejemplo de uso de modulos
    let mut input: String = String::new();
    println!("Escribe los detalles del Item");

    println!("NOMBRE:");
    stdin().read_line(&mut input).unwrap();
    let nombre = input.replace("\n", "").replace("\r", "");

    println!("CANTIDAD:");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let cantidad = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    println!("PRECIO UNITARIO: ");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let precio_unitario = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    // Creando el item con la estructura que importamos de nuestro modulo
    let item: Item = Item {
        nombre,
        precio_unitario,
        cantidad
    };

    // Agregando el item a la compra
    agregar_item(items_compra, item);

    println!("Item Agregado!");
}

fn manejar_quitar_item(items_compra: &mut Vec<Item>) {

    // mostrando los items par que el usuario pueda saber cual quitar
    // REUTILIZANDO NUESTRO MODULO !!!
    println!("Selecciona el indice que quieres quitar");
    mostrar_items(items_compra);

    // Obteniendo el itema a eliminar
    let mut input: String = String::new();
    stdin().read_line(&mut input).unwrap();
    // limpiando el input de la terminal
    let indice = input.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    // Eliminando el item utilizando la funcion dentro del modulo de compra
    quitar_item(items_compra, indice);
    println!("Item eliminado");
}

fn manejar_realizar_pago() {
    println!("En Construccion");
}

fn main() {
    // Creamos un vector para llevar el registro de los items de la compra
    let mut items_compra: Vec<Item> = Vec::new();

    // Iniciamos un loop en el cual vamos a preguntar al usuario la accion a realizar
    // Dependiendo de sus seeleccion vamos a realizar una tarea, todas ellas dependeran
    // de funciones dentro del modulo "compra"
    loop {
        mostrar_menu();

        // Obtenemos la opcion que selecciona el usuario
        let mut opcion: String = String::new();
        stdin().read_line(&mut opcion).unwrap();
        // limpiando el input de la terminal
        let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

        match opcion_seleccionada {
            1 => manejar_agregar_item(&mut items_compra), // Agregar un item
            2 => manejar_quitar_item(&mut items_compra), // quitar un item
            3 => mostrar_items(&items_compra), // mostrar todos los items y sus indices
            4 => println!("Total a pagar: ${}", total_compra(&items_compra)), // mostrando el total a pagar
            5 => manejar_realizar_pago(), // realizar el pago
            6 => break, // terminando el programa
            _ => println!("Opcion Invalida") // la opcion no es valida, el programa continua
        };
    }

    println!("Programa Finalizado");
}

        
        
    

No hemos hecho más que indicarle a nuestro crate raíz de la existencia del módulo de pagos, hagamos un chequeo rápido para ver si tenemos todo sin romperse.

cargo check

A parte de algunos warnings porque no hemos utilizado todavía nada del módulo de pagos se ve que el programa podría compilar satisfactoriamente, ahora sí, agreguemos la funcionalidad del pago en nuestra función manejar_realizar_pago. En src/main.rs

        
        
            // src/main.rs

use std::io::stdin;
use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra};
use caja_supermercado::pago::{MetodoDePago, ResultadoPago, pagar};

fn mostrar_menu() {
    println!("OPCIONES:");
    println!("1. Agregar Item");
    println!("2. Quitar Item");
    println!("3. Mostrar Items");
    println!("4. Total a Pagar");
    println!("5. Realizar Pago");
    println!("6. Cancelar Compra y Salir");
    println!("Selecciona una opcion");
}

fn manejar_agregar_item(items_compra: &mut Vec<Item>) {
    // Solicitando que el usuario registre los datos del item
    // por facilidad no hemos colocado validaciones, en un ambiente
    // laboral SI debes colocarlas.
    // Quiza no es el input mas optimo del planeta pero sirve a manera
    // de ejemplo de uso de modulos
    let mut input: String = String::new();
    println!("Escribe los detalles del Item");

    println!("NOMBRE:");
    stdin().read_line(&mut input).unwrap();
    let nombre = input.replace("\n", "").replace("\r", "");

    println!("CANTIDAD:");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let cantidad = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    println!("PRECIO UNITARIO: ");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let precio_unitario = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    // Creando el item con la estructura que importamos de nuestro modulo
    let item: Item = Item {
        nombre,
        precio_unitario,
        cantidad
    };

    // Agregando el item a la compra
    agregar_item(items_compra, item);

    println!("Item Agregado!");
}

fn manejar_quitar_item(items_compra: &mut Vec<Item>) {

    // mostrando los items par que el usuario pueda saber cual quitar
    // REUTILIZANDO NUESTRO MODULO !!!
    println!("Selecciona el indice que quieres quitar");
    mostrar_items(items_compra);

    // Obteniendo el itema a eliminar
    let mut input: String = String::new();
    stdin().read_line(&mut input).unwrap();
    // limpiando el input de la terminal
    let indice = input.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    // Eliminando el item utilizando la funcion dentro del modulo de compra
    quitar_item(items_compra, indice);
    println!("Item eliminado");
}

fn manejar_realizar_pago(items_compra: &mut Vec<Item>) {

    let monto_a_pagar = total_compra(items_compra);

    println!("Monto a Pagar: ${}", monto_a_pagar);
    println!("Selecciona el metodo de pago.");
    println!("1. En Efectivo");
    println!("2. Tarjeta");
    println!("3. Transferencia Bancaria");

    let mut recibido_del_cliente = 0.0;
    let mut tarjeta = String::from("N/A");

    // Obtenemos la opcion que selecciona el usuario
    let mut opcion: String = String::new();
    stdin().read_line(&mut opcion).unwrap();
    // limpiando el input de la terminal
    let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    let metodo_de_pago = match opcion_seleccionada {
        1 => MetodoDePago::Efectivo,
        2 => MetodoDePago::Tarjeta,
        3 => MetodoDePago::TransferenciaBancaria,
        _ => MetodoDePago::Efectivo // por facilidad
    };

    if opcion_seleccionada == 1 {
        // El metodo de pago es efectivo, necesitamos saber cuanto recibimos del cliente
        println!("Monto Recibido del Cliente:");
        let mut recibido: String = String::new();
        stdin().read_line(&mut recibido).unwrap();
        recibido_del_cliente = recibido.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();
    }

    if opcion_seleccionada == 2 {
        println!("Num. De Tarjeta:");
        // El metodo de pago es con tarjeta, necesitamos saber el numero
        let mut input: String = String::new();
        stdin().read_line(&mut input).unwrap();
        tarjeta = input.replace("\n", "").replace("\r", "");
    }

    let resultado_del_pago: ResultadoPago = pagar(metodo_de_pago, monto_a_pagar, recibido_del_cliente, &tarjeta);

    if resultado_del_pago.fue_exitoso {
        println!("El pago fue exitoso");
        println!("Metodo de Pago: {}", resultado_del_pago.metodo_pago);
        println!("Cambio: ${}", resultado_del_pago.cambio);
    } else {
        println!("Hubo un problema procesando el pago, intentalo de nuevo");
    }

}

fn main() {
    // Creamos un vector para llevar el registro de los items de la compra
    let mut items_compra: Vec<Item> = Vec::new();

    // Iniciamos un loop en el cual vamos a preguntar al usuario la accion a realizar
    // Dependiendo de sus seeleccion vamos a realizar una tarea, todas ellas dependeran
    // de funciones dentro del modulo "compra"
    loop {
        mostrar_menu();

        // Obtenemos la opcion que selecciona el usuario
        let mut opcion: String = String::new();
        stdin().read_line(&mut opcion).unwrap();
        // limpiando el input de la terminal
        let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

        match opcion_seleccionada {
            1 => manejar_agregar_item(&mut items_compra), // Agregar un item
            2 => manejar_quitar_item(&mut items_compra), // quitar un item
            3 => mostrar_items(&items_compra), // mostrar todos los items y sus indices
            4 => println!("Total a pagar: ${}", total_compra(&items_compra)), // mostrando el total a pagar
            5 => manejar_realizar_pago(&mut items_compra), // realizar el pago
            6 => break, // terminando el programa
            _ => println!("Opcion Invalida") // la opcion no es valida, el programa continua
        };
    }

    println!("Programa Finalizado");
}

        
        
    

Ahora si corremos nuestro programa veremos que la opción de pago ahora está habilitada!! 🤠

cargo run

Genial!! Ya nuestro programa está en muy buena forma, algo que quiero que notes es que luego del pago, no estamos borrando el saldo ni los items, por lo que te permitirá pagar más de una vez, en un mundo laboral, esto sería un gravísimo problema, pero para suerte nuestra, este es tan solo un ejemplo de uso de módulos por lo que podremos así 😎

Podríamos dejar el programa hasta acá, per me gustaría que tratásemos ahora de utilizar funciones del método de pago directamente en nuestro método de "compra" y de esta forma mostrarte que podemos utilizar funciones entre módulos.

Vamos a modificar el módulo de "compra" en src/lib.rs

        
        
            // src/lib.rs

pub mod pago {

    pub enum MetodoDePago {
        Efectivo,
        Tarjeta,
        TransferenciaBancaria,
    }

    pub struct ResultadoPago {
        pub metodo_pago: String, // La descripcion del metodo de pago
        pub fue_exitoso: bool, // true si el pago se pudo hacer o false si no se pudo hacer
        pub cambio: f32, // Cambio a devolver al cliente.
    }

    pub fn pagar(metodo_de_pago: MetodoDePago, monto_a_pagar: f32, recibido_del_cliente: f32, tarjeta: &str) -> ResultadoPago{
        // El parametro metodo_de_pago es la forma de pago elegida por el cliente.
        // El parametro monto_a_pagar es el total a pagar de la compra.
        // recibido_del_cliente es la cantidad de dinero recibida del cliente, si no es efectivo, es igual al monto a pagar
        // tarjeta, es el numero de tarjeta del cliente, si el pago es en efectivo o por transferencia, no es necesario, puede ser cualquiera

        // Ahora, dependiendo del metodo de pago elegido por el cliente, invocamos las funciones privadas, esto puede hacerse
        // porque estan dentro del mismo alcance de este metodo.
        let resultado = match metodo_de_pago {
            MetodoDePago::Efectivo => pago_en_efectivo(monto_a_pagar, recibido_del_cliente),
            MetodoDePago::Tarjeta => pago_con_tarjeta(monto_a_pagar, tarjeta),
            MetodoDePago::TransferenciaBancaria => pago_por_transferencia_bancaria(monto_a_pagar)
        };

        resultado
    }

    fn pago_en_efectivo(monto_a_pagar: f32, recibido_del_cliente: f32) -> ResultadoPago {
        // Si el pago es en efectivo, se calculara el cambio a devolver al cliente
        let mut cambio = recibido_del_cliente - monto_a_pagar;
        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        cambio = (cambio * y).round() / y;

        ResultadoPago {
            metodo_pago: String::from("En Efectivo"),
            fue_exitoso: true,
            cambio
        }
    }

    fn pago_con_tarjeta(monto_a_pagar: f32, numero_tarjeta: &str) -> ResultadoPago {
        // Si el pago es con tarjeta, simularemos el resultado

        // Aca se estaria procesando todo aquello critico a nivel de seguridad.
        println!("Realizando el pago con el servicio de tarjeta credito/debito");
        println!("Monto a pagar: {}", monto_a_pagar);
        println!("Tarjeta: {}", numero_tarjeta);

        //Ahora simulamos el resultado
        ResultadoPago {
            metodo_pago: String::from("Tarjeta"),
            fue_exitoso: true,
            cambio: 0.0
        }

    }

    fn pago_por_transferencia_bancaria(monto_a_pagar: f32) -> ResultadoPago {
        // Si el pago es via transferencia, simularemos que solamente necesitamos la cuenta del supermercado
        // la cual seria la cuenta a recibir el dinero y tambien simularemos el resultado de la transferencia
        // Esta cuenta supondriamos que es secreta

        // Aca se estaria procesando todo aquello critico a nivel de seguridad.
        println!("Realizando las conexiones y transferencias con el banco");
        println!("Monto a pagar: {}", monto_a_pagar);
        println!("Cuenta Bancaria Secreta (o no tanto jejeej): 123456789-0");

        //Ahora simulamos el resultado
        ResultadoPago {
            metodo_pago: String::from("Transferencia Bancaria"),
            fue_exitoso: true,
            cambio: 0.0
        }
    }
}

pub mod compra {
    use crate::pago;

    #[derive(Debug)]
    pub struct Item {
        pub nombre: String, // Nombre del item
        pub precio_unitario: f32, // Precio Unitario del item
        pub cantidad: f32, // Cantidad a comprar del item, es float porque pueden ser fracciones de unidades, como kilos
    }

    pub fn agregar_item(items_compra: &mut Vec<Item>, item: Item) {
        // Agrega un item a un vector con todos los items de la compra
        items_compra.push(item);
    }

    pub fn quitar_item(items_compra: &mut Vec<Item>, indice: usize) {
        // Quitara un item del array a partir de un indice
        items_compra.remove(indice);
    }

    pub fn mostrar_items(items_compra: &Vec<Item>) {
        // Mostrando los items y el indice
        for (index, item) in items_compra.iter().enumerate() {
            let subtotal = item.cantidad * item.precio_unitario;
            println!("[{}]. {} - Cantidad: {} - Precio U: ${} - Subtotal: ${}", index, item.nombre, item.cantidad, item.precio_unitario, subtotal);
        }
    }

    pub fn total_compra(items_compra: &Vec<Item>) -> f32 {
        // Devolvera el totla a pagar de todos los items de la compra
        let mut total_compra: f32 = 0.0;
        for item in items_compra {
            total_compra = total_compra + (item.cantidad * item.precio_unitario);
        }

        // redondeando a dos decimales
        let y = 10i32.pow(2) as f32;
        total_compra = (total_compra * y).round() / y;

        total_compra
    }

    pub fn pagar_compra(metodo_de_pago: pago::MetodoDePago, monto_a_pagar: f32, recibido_del_cliente: f32, tarjeta: &str) -> pago::ResultadoPago{
        // El parametro metodo_de_pago es la forma de pago elegida por el cliente.
        // El parametro monto_a_pagar es el total a pagar de la compra.
        // recibido_del_cliente es la cantidad de dinero recibida del cliente, si no es efectivo, es igual al monto a pagar
        // tarjeta, es el numero de tarjeta del cliente, si el pago es en efectivo o por transferencia, no es necesario, puede ser cualquiera

        // Ahora, dependiendo del metodo de pago elegido por el cliente, invocamos las funciones privadas, esto puede hacerse
        // porque estan dentro del mismo alcance de este metodo.

        let resultado = pago::pagar(metodo_de_pago, monto_a_pagar, recibido_del_cliente, tarjeta);

        resultado
    }
}

        
        
    

Si te fijas, dentro del modulo de "compra" estamos utilizando un use.

Rust permite esto para acortar un poco los paths a ser utilizados

y ahora cambiemos un poco nuestro src/main.rs para utilizar la función de pago del módulo de compra:

        
        
            // src/main.rs

use std::io::stdin;
use caja_supermercado::pago::{MetodoDePago, ResultadoPago};
use caja_supermercado::compra::{Item, agregar_item, quitar_item, mostrar_items, total_compra, pagar_compra};

fn mostrar_menu() {
    println!("OPCIONES:");
    println!("1. Agregar Item");
    println!("2. Quitar Item");
    println!("3. Mostrar Items");
    println!("4. Total a Pagar");
    println!("5. Realizar Pago");
    println!("6. Cancelar Compra y Salir");
    println!("Selecciona una opcion");
}

fn manejar_agregar_item(items_compra: &mut Vec<Item>) {
    // Solicitando que el usuario registre los datos del item
    // por facilidad no hemos colocado validaciones, en un ambiente
    // laboral SI debes colocarlas.
    // Quiza no es el input mas optimo del planeta pero sirve a manera
    // de ejemplo de uso de modulos
    let mut input: String = String::new();
    println!("Escribe los detalles del Item");

    println!("NOMBRE:");
    stdin().read_line(&mut input).unwrap();
    let nombre = input.replace("\n", "").replace("\r", "");

    println!("CANTIDAD:");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let cantidad = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    println!("PRECIO UNITARIO: ");
    input = String::new();
    stdin().read_line(&mut input).unwrap();
    let precio_unitario = input.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();

    // Creando el item con la estructura que importamos de nuestro modulo
    let item: Item = Item {
        nombre,
        precio_unitario,
        cantidad
    };

    // Agregando el item a la compra
    agregar_item(items_compra, item);

    println!("Item Agregado!");
}

fn manejar_quitar_item(items_compra: &mut Vec<Item>) {

    // mostrando los items par que el usuario pueda saber cual quitar
    // REUTILIZANDO NUESTRO MODULO !!!
    println!("Selecciona el indice que quieres quitar");
    mostrar_items(items_compra);

    // Obteniendo el itema a eliminar
    let mut input: String = String::new();
    stdin().read_line(&mut input).unwrap();
    // limpiando el input de la terminal
    let indice = input.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    // Eliminando el item utilizando la funcion dentro del modulo de compra
    quitar_item(items_compra, indice);
    println!("Item eliminado");
}

fn manejar_realizar_pago(items_compra: &mut Vec<Item>) {

    let monto_a_pagar = total_compra(items_compra);

    println!("Monto a Pagar: ${}", monto_a_pagar);
    println!("Selecciona el metodo de pago.");
    println!("1. En Efectivo");
    println!("2. Tarjeta");
    println!("3. Transferencia Bancaria");

    let mut recibido_del_cliente = 0.0;
    let mut tarjeta = String::from("N/A");

    // Obtenemos la opcion que selecciona el usuario
    let mut opcion: String = String::new();
    stdin().read_line(&mut opcion).unwrap();
    // limpiando el input de la terminal
    let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

    let metodo_de_pago = match opcion_seleccionada {
        1 => MetodoDePago::Efectivo,
        2 => MetodoDePago::Tarjeta,
        3 => MetodoDePago::TransferenciaBancaria,
        _ => MetodoDePago::Efectivo // por facilidad
    };

    if opcion_seleccionada == 1 {
        // El metodo de pago es efectivo, necesitamos saber cuanto recibimos del cliente
        println!("Monto Recibido del Cliente:");
        let mut recibido: String = String::new();
        stdin().read_line(&mut recibido).unwrap();
        recibido_del_cliente = recibido.replace("\n", "").replace("\r", "").parse::<f32>().unwrap();
    }

    if opcion_seleccionada == 2 {
        println!("Num. De Tarjeta:");
        // El metodo de pago es con tarjeta, necesitamos saber el numero
        let mut input: String = String::new();
        stdin().read_line(&mut input).unwrap();
        tarjeta = input.replace("\n", "").replace("\r", "");
    }

    let resultado_del_pago: ResultadoPago = pagar_compra(metodo_de_pago, monto_a_pagar, recibido_del_cliente, &tarjeta);

    if resultado_del_pago.fue_exitoso {
        println!("El pago fue exitoso");
        println!("Metodo de Pago: {}", resultado_del_pago.metodo_pago);
        println!("Cambio: ${}", resultado_del_pago.cambio);
    } else {
        println!("Hubo un problema procesando el pago, intentalo de nuevo");
    }

}

fn main() {
    // Creamos un vector para llevar el registro de los items de la compra
    let mut items_compra: Vec<Item> = Vec::new();

    // Iniciamos un loop en el cual vamos a preguntar al usuario la accion a realizar
    // Dependiendo de sus seeleccion vamos a realizar una tarea, todas ellas dependeran
    // de funciones dentro del modulo "compra"
    loop {
        mostrar_menu();

        // Obtenemos la opcion que selecciona el usuario
        let mut opcion: String = String::new();
        stdin().read_line(&mut opcion).unwrap();
        // limpiando el input de la terminal
        let opcion_seleccionada = opcion.replace("\n", "").replace("\r", "").parse::<usize>().unwrap();

        match opcion_seleccionada {
            1 => manejar_agregar_item(&mut items_compra), // Agregar un item
            2 => manejar_quitar_item(&mut items_compra), // quitar un item
            3 => mostrar_items(&items_compra), // mostrar todos los items y sus indices
            4 => println!("Total a pagar: ${}", total_compra(&items_compra)), // mostrando el total a pagar
            5 => manejar_realizar_pago(&mut items_compra), // realizar el pago
            6 => break, // terminando el programa
            _ => println!("Opcion Invalida") // la opcion no es valida, el programa continua
        };
    }

    println!("Programa Finalizado");
}

        
        
    

Si ejecutamos ahora nuestro programa, veremos que todo sigue funcionando tal cual lo hemos construido!! 😃

A lo mejor nuestro programa no es perfecto, porque hay muchas validaciones que hemos obviado por facilidad del ejemplo, como una tarea voluntaria puedes agregarlas y perfeccionar nuestro programa 😊

Si quieres ver el código completo de nuestro ejemplo puedes hacerlo desde el repositorio en github dando click a este enlace.

A lo mejor te parecerá que lejos de organizar mejor el código, hemos escrito demasiadas líneas en un mismo archivo, y en efecto, tienes razón, es por eso que Rust nos permite separar nuestro módulos en distintos archivos, de esta forma tendremos archivos muchos más sostenibles, sin embargo, es necesario conocer estos conceptos de módulos para poder trabajarlos bien, en nuestro siguiente post haremos otro ejemplo, pero ahora separando nuestro código en pequeños archivos (módulos) mucho mas fáciles de darle mantenimiento.

Recuerda que siempre puedes leer la primera parte de esta miniserie de organización de código con Rust acá:

Organizar Código En Rust - Paquetes y Librerías

Si este post te ha sido de utilidad compártelo con tus amigos y en tus redes sociales, también no olvides dejar tu comentario sobre lo que te ha gustado o no te ha gustado de esta publicación y aquellos temas que te gustaría aprender 😃.

println!("Hasta la próxima!");


 Utilizamos cookies propias y de terceros para mejorar tu experiencia, mostrar publicidad y análisis de navegación, puedes encontrar el detalle en nuestra Política de Cookies