Creando un programa FizzRust...FizzBuzz.

⚠️ LECTURA PROLONGADA ⚠️

Hemos aprendo a las bases para utilizar nuestras herramientas de Rust y creamos nuestro primer "Hola Mundo" en poco tiempo, pero ahora pasemos a crear un programa más interesante, un programa llamado "FizzBuzz".

¿Fizz..que?

FizzBuzz es un juego que se utiliza con los niños para mejorar su capacidad de aplicar una división de manera mental. No, no planeamos tratarte como un niño pues esta prueba también se utiliza en las entrevistas de programación y funciona como un filtro, normalmente se les pide lo siguiente a los candidatos:

"Escriba un programa que imprima los números desde 1 hasta 100. Pero en cada número que sea múltiplo de 3 se deberá imprimir "Fizz" en el lugar del número, el mismo caso para los números que sean múltiplos de 5. Por cada número que sea múltiplo de 3 y 5 se deberá imprimir FizzBuzz"

Por más simple que parezca la prueba, muchos programadores no logran pasarla pues no es un patrón que se vea dentro de las enseñanzas clásicas y por que no es posible representar las pruebas de manera directa y simple sin necesidad de duplicar algo.

Aun así no nos vamos a adentrar en la complejidad de la prueba y explicar los árboles de desiciones en este capítulo sería desviarnos del objetivo principal, existen diferentes formas de resolver un problema FizzBuzz en diferentes lenguajes, aquí puedes ver algunos ejemplos.

Python

def fizzbuzz(x):
    """Simple FizzBuzz, courtesy of @RodolfoFerro on @GitHub"""

    if x % 3 == 0 and x % 5 == 0:
        return "FizzBuzz"
    elif x % 3 == 0:
        return "Fizz"
    elif x % 5 == 0:
        return "Buzz"
    else:
        return str(x)

if __name__ == '__main__':
    sequence = '\n'.join(fizzbuzz(x) for x in range(1, 100))
    print(sequence)

C (ANSI + Kernel Coding Style)

#include <stdio.h>

int main(int argc, char *argv[])
{
        for (int i = 1; i <= 100; i++) {
                if (i % 3 == 0)
                        printf("Fizz");
                if (i % 5 == 0)
                        printf("Buzz");
                if (i % 3 != 0 && i % 5 != 0)
                         printf("%d", i);

                printf("\n");
        }
        return 0;
}

El problema con este ejemplo es que no se ve un caso para el "FizzBuzz". Nótese que no existe la impresión de una nueva línea en el cuarto printf. Si el número es un múltiplo de 3 y 5 las primeras dos condiciones se cumplen, por lo tanto ambos casos se ejecutan.

Si deseas agregar un ejemplo de FizzBuzz al libro eres bienvenido, solo abre un Pull Request que incluya el programa con tu lenguaje de programación favorito :D

Bien, ahora vamos a tomar la aproximación de Rust. En este proyecto crearemos el mismo programa pero con salida de colores y en el camino explicaremos por encima algunos conceptos base de programación en Rust.

Preparando el proyecto

Utilizando nuestros conocimientos previos lo primero que haremos será crear un nuevo proyecto utilizando Cargo, vamos a llamarlo FizzBuzz.

$ cargo new --bin fizzbuzz

Ahora entremos en el directorio creado por Cargo:

$ cd fizzbuzz

Vamos a editar el archivo Cargo.toml el cual se encuentra por defecto, en nuestro caso los programadores de Future Lab decidieron dejarlo de esta manera:

[package]
name = "fizzbuzz"
version = "0.1.0"
authors = ["Future Lab <mxfuturelab@futuremail.com>"]
edition = "2018"

[dependencies]

En la parte superior mencionamos que nuestro programa tendría una salida a terminal de colores, para esto necesitamos agregar un crate a nuestra sección [dependencies]. La crate que utilizaremos es colored un paquete de Rust que nos permite imprimir texto con decoraciones en la terminal.

Nuestro archivo Cargo.toml debería quedar así:

[package]
name = "fizzbuzz"
version = "0.1.0"
authors = ["Future Lab <mxfuturelab@futuremail.com>"]
edition = "2018"

[dependencies]
colored = "1.6"

Bien, tenemos las dependencias que necesitamos para nuestro programa en Rust, digámosle a Cargo que las descargue y las compile para poder utilizarlas en nuestro proyecto sin problema alguno con:

$ cargo build

Esto debería de descargar y compilar colored y dejarlo listo para usar:

   Updating crates.io index
   Compiling lazy_static v1.2.0
   Compiling colored v1.6.1
   Compiling fizzbuzz v0.1.0 (/home/futurelab/Escritorio/fizzbuzz)
    Finished dev [unoptimized + debuginfo] target(s) in 13.38s

Escribir una base

Utilizando el editor de tu preferencia abre el archivo main.rs, este es el archivo que vamos a modificar, elimina la línea que contiene el mensaje de impresión (println!) y procede a escribir el siguiente código, no te preocupes si no lo entiendes pues lo explicaremos a lo largo de este capítulo.

fn main() {
    for x in 0..{
        match (x % 3, x % 5) {
            (0, 0) => println!("FizzBuzz"),
            (0, _) => println!("Fizz"),
            (_, 0) => println!("Buzz"),
            (_, _) => println!("{}", x)
        }
    }
}

No ejecutes este código aún, es una versión de pruebas que modificaremos a lo largo de este y los siguientes capítulos.

Comprendiendo el código

¿Que está pasando aquí? Hay muchos elementos nuevos (Claro, todo es nuevo a diferencia del macro println! que vimos en el capítulo pasado) por lo que vamos a volver a descomponer línea por línea todo el programa.

Revisemos dentro de la función main, esta función es la misma que utilizamos en nuestro primer hola mundo por lo que no hay necesidad de explicarla así que nos dirigiremos a la segunda línea de nuestro código actual:

    for x in 0..{

El iterador

Como mencionamos en el capítulo 2, asumiremos que posees conocimientos básicos de programación por lo que debe ser evidente que esto es algo conocido como "ciclo for", los exploraremos más adelante en el capítulo de control de flujo, por ahora vamos a dejar claras las bases de un ciclo for en Rust.

Lo primero que podemos observar es que la sintaxis de los ciclos for no es similar a la utilizada en lenguajes como C, JavaScript o Java:

for (x = 0; x < 10; x++) {
        printf("%i\n", x);
}

En su lugar (y en términos más abstractos) los ciclos for en Rust se ven algo así:

for x in 0..5 {
    println!("{}", x);
}

Si abstraemos los términos podríamos describirlos así:

for variable in expresion {
    código
}

Siendo "expresion" un iterador. En nuestro ejemplo elegimos el rango de 0..5, esta expresión consta de un inicio y un fin y el ciclo realizará una iteración entre esos valores, aunque Rust posee rangos inclusivos, por defecto el límite superior es exclusivo, por lo tanto nuestro ciclo for imprimirá los valores del 0 al 4, no hasta el número 5.

La razón por la que Rust no posee ciclos for con una sintaxis parecida a C es sencilla, al tener esa sintaxis se controla manualmente cada elemento del ciclo lo cual deja el código susceptible a errores humanos.

Pero hay algo diferente en nuestro ciclo for, si volvemos por un momento a nuestro código podemos notar que no tenemos un valor definido como límite superior del ciclo:

    for x in 0..{

En su lugar tenemos una llave abierta, indicando el inicio de las instrucciones del ciclo. Esto es muy sencillo, cuando Rust encuentre un ciclo for de este tipo lo ejecutará infinitamente.

Nos hemos equivocado a propósito, pues Rust posee una forma más sencilla de realizar ciclos infinitos.

Por ahora continuaremos explorando la estructura del ciclo for:

        match (x % 3, x % 5) {
            (0, 0) => println!("FizzBuzz"),
            (0, _) => println!("Fizz"),
            (_, 0) => println!("Buzz"),
            (_, _) => println!("{}", x)

Múltiples casos, múltiples resultados.

Aquí podemos ver otro elemento "antes de tiempo" que viene del control de flujo en Rust, en este caso match.

match es el equivalente a switch en otros lenguajes de programación como C o C++, una alternativa a comparar y ejecutar los diferentes valores que puede tomar una variable.

La estructura de la palabra clave match puede ser utilizada de formas complejas y aunque no explicaremos a fondo su uso por ahora, intentaremos explicarte como funciona match en el caso de nuestro código.

Para que match funcione correctamente necesita una expresión para evaluar, como nosotros tenemos x como variable (declarada en el alcance del ciclo for) y ésta se encuentra en uso como iterador su valor mutará en cada "vuelta" del ciclo, en este caso x aumentará su valor en una unidad por iteración por lo que x será lo que evaluaremos.

Dentro de los paréntesis tenemos dos operaciones que procesarán x y arrojarán un resultado que será enviado a cualquiera de los posibles casos dentro de match, en este caso nuestras dos operaciones son las siguientes: x % 3 y x % 5.

El operador % cumple la misma función que en otros lenguajes, retorna el residuo de la división entre dos números, sabiendo esto cada iteración comparará a x dos veces, regresando el residuo del valor de x dividido entre 3 y 5, dependiendo del resultado se enviará a la pantalla el mensaje.

Veamos la primera condición:

            (0, 0) => println!("FizzBuzz"),

Si el residuo resultante de la división de x/3 Y x/5 es igual a cero entonces se enviará a la pantalla el mensaje "FizzBuzz".


En la segunda condición las cosas cambian un poco, podemos observar un elemento nuevo, un guión bajo (_), éste funciona de la misma manera que un default: funciona dentro de un switch.

En este caso _ funciona como un "atrapa-todo" en el cual caerán todos los resultados que no coincidan don las condiciones expresadas en los casos de match. Si observamos detenidamente el código podemos llegar a la conclusión de que _ funcionará en caso de que la división de x/3 ó x/5 arrojen cualquier valor diferente de 0

            (0, _) => println!("Fizz"),
            (_, 0) => println!("Buzz"),
            (_, _) => println!("{}", x)

Agregando algo de color

Bien, nuestro programa FizzBuzz funciona, en un ciclo infinito que se ejecutará hasta que nuestra computadora tenga la necesidad de detenerlo, más tarde arreglaremos ese problema, por ahora vamos a darle un poco de color a la salida del programa. Para esto utilizaremos un crate llamado colored, la misma que agregamos al inicio de este capítulo, el uso es sencillo, por ejemplo, podemos imprimir "Hola Rust" en color rojo:

extern crate colored;

use colored::*;

fn main() {
    println!("{}", "¡Hola Rust!".red());
}

Eliminamos la opción de ejecución en este ejemplo, pues el navegador no soporta la impresión de texto de color.

Hagamos una ligera modificación, cuando el programa tenga que imprimir "Fizz", lo hará en un color rojo, en caso de que necesite imprimir "Buzz" será en color amarillo y en el caso de imprimir "FizzBuzz" lo hará de color cían.

(No es necesario que pongas esos colores si no son de tu agrado, en la guía oficial del crate están listados todos los colores y estilos disponibles, anímate a experimentar un poco).

Veamos el código utilizando colores de Future Lab :D

extern crate colored;

use colored::*;

fn main() {
    for x in 0..{
        match (x % 3, x % 5) {
            (0, 0) => println!("{}","FizzBuzz".cyan()),
            (0, _) => println!("{}","Fizz".red()),
            (_, 0) => println!("{}","Buzz".yellow()),
            (_, _) => println!("{}", x)
        }
    }
}

Aun más elementos nuevos, vamos a explicarlos parte por parte:

La declaración extern crate le especifica a Rust que nuestro programa depende de un biblioteca externa a nuestro proyecto, acercándola a nuestro alcance.

extern crate colored;

La declaración use crea enlaces locales a funciones o métodos remotos, se utiliza para simplificar el uso de localización de archivos.

use colored::*;

Las siguientes líneas son sencillas de explicar:

            (0, 0) => println!("{}","FizzBuzz".cyan()),
            (0, _) => println!("{}","Fizz".red()),
            (_, 0) => println!("{}","Buzz".yellow()),
            (_, _) => println!("{}", x)

Nuestro macro println! ha cambiado, en este caso el crate colored solo funciona con cadenas de caracteres, por lo que necesitamos colocar algo llamado "placeholder" que en resumen es un espacio donde se colocará un elemento más tarde, tenemos que separar con una coma los argumentos, en ese caso el segundo argumento será nuestra cadena, en este caso "Fizz", "Buzz", y "FizzBuzz", solo resta agregar la función correspondiente. colored retorna cadenas en todas sus funciones por lo que no habrá problema si pasamos las funciones seguidas de un punto en este caso.

La impresión del programa debería verse de esta manera en una ejecución normal:

Salida De Colores

Se ve genial ¿no?, puedes probar a hacer diferentes combinaciones, por ejemplo al imprimir "FizzBuzz" hacer que cada carácter tenga un color distinto:

Salida De Colores

¡Genial! Nuestro programa ahora imprime las cosas de una manera más "elegante". Pero podemos notar un problema en las imágenes, los números evaluados son muy altos, no deberíamos de cargar al sistema con esa clase de operaciones.

Podríamos colocar un límite superior en el ciclo for en nuestro código, pero, si bien es una buena solución la verdad sea dicha, tenemos planeado enseñarte algo que causa muchos problemas con los principiantes en el lenguaje de programación Rust.

Operaciones de entrada

Entre los usuarios Novel, pedir entrada de datos al usuario es un tema común, pues no existe una manera sencilla de hacerlo como en otros lenguajes como C, Python o Ruby.

Esto tiene una razón sencilla, una entrada errónea del usuario puede causar comportamientos inesperados en los programas que podamos crear, puede parecer algo tedioso al inicio y es algo que se ha estado trabajando desde las versiones mas nuevas de Rust, aun así, las comparaciones entre Rust y otros lenguajes como Python y Ruby, carecen de sentido, pues cumplen roles diferentes.

Vamos a pedir al usuario ingresar un número entero, el cual servirá como límite superior de nuestro ciclo for, con ello el usuario tendrá el control sobre las iteraciones de nuestro programa FizzBuzz.

En este caso, si el usuario ingresa cualquier cosa que no sea un número entero se interpretará como un error irrecuperable y el programa se cerrará inmediatamente.

Primero necesitamos importar las bibliotecas necesarias para que el proyecto funcione, tendremos que crear una manera de pedir entrada al usuario y finalmente comprobar que nuestro programa funcione.

El código completo debería verse de la siguiente manera:

extern crate colored;

use colored::*;
use std::io;

fn main() {
    let mut entrada = String::new();
    println!("Ingrese el número de iteraciones deseadas:");
    io::stdin().read_line(&mut entrada).unwrap();

    let iter: i32 = entrada.trim().parse().unwrap();

    for x in 0..=iter {
        match (x % 3, x % 5) {
            (0, 0) => println!("{}", "FizzBuzz".cyan()),
            (0, _) => println!("{}", "Fizz".red()),
            (_, 0) => println!("{}", "Buzz".yellow()),
            (_, _) => println!("{}", x),
        }
    }
}

De nuevo hay muchas cosas nuevas en este código, cosa que nos beneficiará pues los temas posteriores serán mas sencillos de digerir una vez comprendamos como funcionan con este tipo de ejemplos.

Nuestro primer paso es "importar las bibliotecas necesarias".

use std::io;
//-- Mas código

Rust viene preparado con toda clase de cosas en su biblioteca estándar, sin embargo, si tuviésemos que importar cada cosa que utilizamos en nuestro código al final tendríamos un desastre. Al mismo tiempo, importar toneladas de bibliotecas a nuestro programa para dejar un gran porcentaje de éstas sin usar tampoco es algo bueno, por lo que un balance es necesario. Por lo que necesitaremos cargar las funciones de entrada y salida de la biblioteca estándar.

Una vez tengamos las bibliotecas necesarias procederemos a pedir una entrada al usuario:

//-- Recorte
    let mut entrada = String::new();
    println!("Ingrese el número de iteraciones deseadas:");
    io::stdin().read_line(&mut entrada).unwrap();
//-- Recorte

Primero necesitamos un lugar donde almacenar la entrada del usuario:

    let mut entrada = String::new();

La declaración de variables se realiza con la palabra reservada let en Rust, cubriremos eso mas tarde, por ahora todo lo que tienes que saber es que con esta línea Rust está reservando una variable mutable (mut) llamada entrada (Si vienes de un lenguaje como Java podrás notar que es un método similar al de un Scanner, solo que con el estilo de Rust).

Al igual que en otros lenguajes el operador = se utiliza para realizar una asignación, en el caso de nuestra línea de código estamos asignando el valor retornado por una función String::new, en este caso el valor que regresa la función es una nueva cadena.

Similar a C++, Rust utiliza una sintáxis de 4 puntos para las funciones asociadas, por eso al llamar ::new() estamos indicándole a Rust que new es una función asociada que retornará un String.

En resumen la línea:

    let mut entrada = String::new();

Crea una nueva variable con una instancia vacía de un String.

Ahora necesitamos indicar a nuestro usuario sus instrucciones, este es un paso importante puesto que tenemos que ser claros, como nuestros usuarios son inteligentes, asumiremos que saben lo que significa iteraciones y procederemos a imprimir un mensaje en la pantalla.

    println!("Ingrese el número de iteraciones deseadas:");

Ahora necesitamos hacer algo para que el usuario ingrese el número y procesarlo para que se almacene en nuestra variable entrada:

    io::stdin().read_line(&mut entrada).unwrap();

Como al inicio del programa utilizamos la línea use std::io; ahora tenemos las funciones y métodos de entrada de texto necesarios para trabajar, al llamar a la función stdin esta retornará una instancia de std::io::Stdin, lo que nos permitirá manejar la entrada estándar desde la terminal.

La siguiente parte del código es .read_line(&mut entrada), con ello llamamos a la función .read_line y el argumento le indica a la función que deberá guardar la entrada del usuario en una referencia para usarla mas tarde.

La última parte es la función .unwrap(), con la cual compararemos la entrada del resultado. Como indicamos al inicio de este capítulo, asumiremos que los errores son irrecuperables y unwrap() nos ayudará con ello, al llamar a ésta función estamos indicándole a Rust lo siguiente: "Si bien esto puede o no tener un valor, yo afirmo que lo tiene. En caso contrario, el programa fallará, no quiero un error recuperable".

¡Listo!, con esto podemos pedir al usuario que ingrese "algo", pero no nos basta con que ingrese "algo", necesitamos que el usuario ingrese un número y cerrar el programa si ingresa cualquier cosa que no sea considerada un número:

    let iter: i32 = entrada.trim().parse().unwrap();

A estas alturas ya sabemos lo que hace let, solo como recordatorio, estamos declarando una variable nueva, llamada iter (por iteraciones), pero hay algo nuevo aquí, específicamente : i32. ¿Qué es esto? Simple, estamos haciendo algo llamado "tipeado", en este caso estamos indicando que nuestra variable iter será tratada como un "entero de 32 bits" (No necesitamos entrar a detalle en esto, pues lo cubriremos en capítulos posteriores), después volvemos a utilizar el operador de asignación pues necesitamos que nuestra variable tenga un valor, en este caso la asignación consta de tres partes:

  • trim(): Esta función eliminará el salto de línea que el usuario ingresa al final de la función stdin, sin ésta función no podremos procesar el salto de línea y unwrap() lo detectará como un error.

  • parse(): Aquí haremos algo llamado parsing o conversión de tipos. Como podemos recordar, lo que ingresó nuestro usuario hasta ahora es una cadena, así esté conformada por números Rust por el momento piensa que es una cadena de caracteres, con esta función estamos convirtiendo esa cadena al tipo de la variable que se le está asignando, en este caso, convertimos una cadena a un entero de 32 bits.

  • unwrap() ya lo hemos cubierto, pero se asegurará de que la conversión resultante devuelva un entero de 32 bits, en caso contrario retornará un error, haciendo que el programa detenga su ejecución.

Bien, ya solo nos falta asignar el número ingresado por el usuario a nuestro ciclo for para completar nuestro programa.

Si prestaste atención notarás que el ciclo for tiene un pequeño cambio, vamos a verlo:

    for x in 0..=iter {

¡Hemos cambiado la estructura de nuestro ciclo! No es nada de que alarmarse, hemos realizado el cambio para tomar ventaja de la edición 2018 de Rust la cual nos permite utilizar ciclos inclusivos, si recordamos las reglas de los ciclos for escritas anteriormente podremos recordar lo siguiente:

"[...] por lo tanto nuestro ciclo for imprimirá los valores del 0 al 4, no hasta el número 5."

Al utilizar la notación ..= el rango será inclusivo por lo que el ciclo se repetirá el número de veces indicado, con esto evitamos realizar una operación extra sobre la entrada del usuario.

Una vez realizadas las correcciones ya podemos ejecutar nuestro programa.

¡Genial! Ahora ya sabes como realizar un programa FizzBuzz en Rust, es probable que tengas muchas preguntas en este momento ¿Por qué las variables mutan? ¿Qué es un ciclo? ¿Cuantos tipos de dato existen? ¿Cuales son los principios de Rust?

Bien, esta y tus preguntas las resolveremos en el siguiente capítulo del libro en el cual trataremos conceptos básicos de programación, si deseas el código fuente del programa lo puedes conseguir en el siguiente enlace.