% Juego de Adivinanzas

Para nuestro primer proyecto, implementaremos un problema clásico de programación para principiantes: un juego de adivinanzas. Como funciona el juego: Nuestro programa generara un numero entero aleatorio entre uno y cien. Nos pedira que introduzcamos una corazonada. Despues de haber proporcionado nuestro numero, este nos dirá si estuvimos muy por debajo y muy por encima. Una vez que adivinemos el numero correcto, nos felicitara. Suena bien?

Configuración Inicial

Creemos un nuevo proyecto. Anda a tu directorio de proyectos. Recuerdas como creamos nuestra estructura de directorios y un Cargo.toml para hola_mundo? Cargo posse un comando que hace eso por nosotros. Probemoslo:

$ cd ~/proyectos
$ cargo new adivinanzas --bin
$ cd adivinanzas

Pasamos el nombre de nuestro proyecto a cargo new, junto con el flag --bin, debido a que estamos creando un binario, en vez de una biblioteca.

Echa un vistazo al Cargo.toml generado:

[package]

name = "adivinanzas"
version = "0.1.0"
authors = ["Tu Nombre <[email protected]>"]

Cargo obtiene esta información de tu entorno. Si no esta correcta, corrigela.

Finalmente, Cargo ha generado un ‘Hola, mundo!’ para nosotros. Echa un vistazo a src/main.rs:

fn main() {
    println!("Hello, world!");
}

Tratemos de compilar lo que Cargo nos ha proporcionado:

$ cargo build
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)

Excelente! Abre tu src/main.rs otra vez. Estaremos escribiendo todo nuestro codigo en este archivo.

Antes de continuar, dejame mostrarte un comando mas de Cargo: run. cargo run es una especie de cargo build, pero con la diferencia de que tambien ejecuta el binario producido. Probemoslo:

$ cargo run
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/debug/adivinanzas`
Hola, mundo!

Grandioso! El comando run es muy útil cuando necesitas iterar rapido en un proyecto. Nuestro juego es uno de esos proyectos, necesitaremos probar rapidamente cada iteración antes de movernos a la siguiente.

Procesando un Intento de Adivinanza

Probemoslo! La primera cosa que necesitamos hacer para nuestro juego de adivinanzas es permitir a nuestro jugador ingresar un intento de adivinanza. Coloca esto en tu src/main.rs:

use std::io;

fn main() {
    println!("Adivina el numero!");

    println!("Por favor introduce tu corazonada.");

    let mut corazonada = String::new();

    io::stdin().read_line(&mut corazonada)
        .ok()
        .expect("Fallo al leer linea");

    println!("Tu corazonada fue: {}", corazonada);
}

Hay un monton aqui! Tratemos de ir a traves de ello, pieza por pieza.

use std::io;

Necesitaremos recibir entrada del usuario, para luego imprimir el resultado como salida. Debido a esto necesitamos la biblioteca io de la biblioteca estandar. Rust solo importa unas pocas cosas para todos los programas, este conjunto de cosas se denomina ‘preludio’. Si no esta en el preludio tendrás que llamarlo directamente a traves de use.

fn main() {

Como has visto con anterioridad, la función main() es el punto de entrada a tu programa. La sintaxis fn declara una nueva función, los ()s indican que no hay argumentos y { comienza el cuerpo de la función. Debido a que no incluimos un tipo de retorno, se asume ser () una tupla vacía.

    println!("Adivina el numero!");

    println!("Por favor introduce tu corazonada.");

Anteriormente aprendimos que println!() es una macro que imprime una cadena de caracteres a la pantalla.

    let mut corazonada = String::new();

Ahora nos estamos poniendo interesantes! Hay un montón de cosas pasando en esta pequeña linea. La primera cosas a notar es que es una sentencia let, usada para crear variables. Tiene la forma:

let foo = bar;

Esto creara una nueva variable llamada foo, y la enlazara al valor bar. En muchos lenguajes, esto es llamado una ‘variable’ pero las variables de Rust tienen un par de trucos bajo la manga.

Por ejemplo, son immutables por defecto. Es por ello que nuestro ejemplo usa mut: esto hace un binding mutable, en vez de inmutable. let no solo toma un nombre del lado izquierdo, let acepta un ‘patrón’. Usaremos los patrones un poco mas tarde. Es suficiente por ahora usar:

let foo = 5; // inmutable.
let mut bar = 5; // mutable

Ah, // inicia un comentario, hasta el final de la linea. Rust ignora todo en comentarios

Entonces sabemos que let mut corazonada introducirá un binding mutable llamado corazonada, pero tenemos que ver al otro lado del = para saber a que esta siendo asociado: String::new().

String es un tipo de cadena de caracter, proporcionado por la biblioteca estandar. Un String es un segmento de texto codificado en UTF-8 capaz de crecer.

La sintaxis ::new() usa :: porque es una ‘función asociada’ de un tipo particular. Es decir esta asociada con String en si mismo, en vez de con una instacia en particular de String. Algunos lenguajes llaman a esto un ‘metodo estatico’.

Esta funcion es llamada new(), porque crea un nuevo String vacio. Encontraras una función new() en muchos tipos, debido a que es un nombre común para la creación de un nuevo valor de algun tipo.

Continuemos:

    io::stdin().read_line(&mut corazonada)
        .ok()
        .expect("Fallo lectura de linea");

Otro monton! Vallamos pieza por pieza. La primera linea tiene dos partes. He aqui la primera:

io::stdin()

Recuerdas como usamos use en std::io en la primera linea de nuestro programa? Ahora estamos llamando una función asociada en std::io. De no haber usado use std::io, pudimos haber escrito esta linea como std::io::stdin().

Esta función en particular retorna un handle a la entrada estándar de tu terminal. Mas especificamente, un std::io::Stdin.

La siguiente parte usara dicho handle para obtener entrada del usuario:

.read_line(&mut corazonada)

Aqui, llamamos el metodo read_line() en nuestro handle. Los metodos son similares a las funciones asociadas, pero solo estan disponibles en una instancia en particular de un tipo, en vez de en el tipo en si. También estamos pasando un argumento a read_line(): &mut corazonada.

Recuerdas cuando creamos corazonada? Dijimos que era mutable. Sin embargo read_line no acepta un String como argumento: acepta un &mut String. Rust posee una caracteristica llamada ‘referencias’ (‘references’), la cual permite tener multiples referencias a una pieza de data, de esta manera se reduce la necesidad de copiado. Las referencias son una caracteristica compleja, debido a que uno de los puntos de venta mas fuertes de Rust es acerca de cuan fácil y seguro es usar referencias. Por ahora no necesitamos saber mucho de esos detalles para finalizar nuestro programa. Todo lo que necesitamos saber por el momento es que al igual que los bindings let las referencias son inmutables por defecto. Como consecuencia necesitamos escribir &mut corazonada en vez de &corazonada.

Porque read_line() acepta una referencia mutable a una cadena de caracteres. Su trabajo es tomar lo que el usuario ingresa en la entrada estandar, y colocarlo en una cadena de caracteres. Debido a ello toma dicha cadena como argumento, y debido a que debe de agregar la entrada del usuario, este necesita ser mutable.

Todavia no hemos terminado con esta linea. Si bien es una sola linea de texto, es solo la primera parte de una linea logica de código completa:

        .ok()
        .expect("Fallo lectura de linea");

Cuando llamas a un metodo con la sintaxis .foo() puedes introducir un salto de linea y otro espacio. Esto te ayuda a dividir lineas largas. Pudimos haber escrito:

    io::stdin().read_line(&mut corazonada).ok().expect("Fallo lectura de linea");

Pero eso es mas difícil de leer. Así que lo hemos dividido en tres lineas para tres llamadas a metodo. Ya hemos hablado de read_line(), pero que acerca de ok() y expect()? Bueno, ya mencionamos que read_line() coloca la entrada del usuario en el &mut String que le proprocionamos. Pero tambien retorna un valor: en este caso un io::Result. Rust posee un numero de tipos llamados Result en su biblioteca estandar: un Result generico, y versiones especificas para sub-bibliotecas, como io::Result.

El proposito de esos Result es codificar información de manejo de errores. Valores del tipo Result tienen metodos definidos en ellos. En este caso io::Result posee un metodo ok(), que se traduce en ‘queremos asumir que este valor es un valor exitoso. Sino, descarta la información acerca del error’. Porque descartar la información acerca del error?, para un programa básico, queremos simplemente imprimir un error generico, cualquier problema que signifique que no podamos continuar. El metodo ok() retorna un valor que tiene otro metodo definito en el: expect(). El metodo expect() toma el valor en el cual es llamado y si no es un valor exitoso, hace panico panic! con un mensaje que le hemos proporcionado. Un panic! como este causara que el programa tenga una salida abrupta (crash), mostrando dicho mensaje.

Si quitamos las llamadas a esos dos metodos, nuestro programa compilara, pero obtendremos una advertencia:

$ cargo build
  Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
src/main.rs:10:5: 10:44 warning: unused result which must be used, #[warn(unused_must_use)] on by default
src/main.rs:10     io::stdin().read_line(&mut corazonada);
                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Rust nos advierte que no hemos usado el valor Result. Esta advertencia viene de una anotación especial que tiene io::Result. Rust esta tratando de decirte que no has manejado un posible error. La manera correcta de suprimir el error es, en efecto escribir el código para el manejo de erroes. Por suerte, si solo queremos terminar la ejecución del programa de haber un problema, podemos usar estos dos pequeños metodos. Si pudieramos recuperarnos del error de alguna manera, hariamos algo diferente, pero dejemos eso para un proyecto futuro.

Solo nos queda una linea de este primer ejemplo:

    println!("Tu corazonada fue: {}", corazonada);
}

Esta linea imprime la cadena de caracteres en la que guardamos nuestra entrada. Los {}s son marcadores de posición, es por ello que pasamos adivinanza como argumento. De haber habido multiples {}s, debiamos haber pasado multiples argumentos:

let x = 5;
let y = 10;

println!("x y y: {} y {}", x, y);

Fácil.

De cualquier modo, ese es el tour. Podemos ejecutarlo con cargo run:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/debug/adivinanzas`
Adivina el numero!
Por favor introduce tu corazonada.
6
Tu corazonada fue: 6

En hora buena! Nuestra primera parte ha terminado: podemos obtener entrada del teclado e imprimirla de vuelta.

Generando un numero secreto

A continuación necesitamos generar un numero secreto. Rust todavía no incluye una funcionalidad de numeros aleatorios en la biblioteca estándar. Sin embargo, el equipo de Rust provee un crate rand. Un ‘crate’ es un paquete de código Rust. Nosotros hemos estado construyendo un ‘crate binaro’, el cual es un ejecutable. rand es un ‘crate biblioteca’, que contiene codigo a ser usado por otros programas.

Usar crates externos es donde Cargo realmente brilla. Antes que podamos escribir código que haga uso de rand, debemos modificar nuestro archivo Cargo.toml. Abrelo, y agrega estas lineas al final:

[dependencies]

rand="0.3.0"

La sección [dependencies] de Cargo.toml es como la sección [package]: todo lo que le sigue es parte de ella, hasta que la siguiente sección comience. Cargo usa la sección de dependencias para saber en cuales crates externos dependemos, asi como las versiones requeridas. En este caso hemos usado la versión 0.3.0. Cargo entiende Versionado Semantico, que es un estandar para las escritura de numeros de versión. Si quisieramos usar la ultima version podriamos haber usado * o un rango de versiones. La documentación de Cargo contiene mas detalles.

Ahora, sin cambiar nada en nuestro código, construyamos nuestro proyecto:

$ cargo build
    Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading rand v0.3.8
 Downloading libc v0.1.6
   Compiling libc v0.1.6
   Compiling rand v0.3.8
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)

(Podrias ver versiones diferentes, por supuesto.)

Un montón de salida mas! Ahora que tenemos una dependencia externa, Cargo descarga del registro las ultimas versiones de todo, lo cual puede copiar datos desde Crates.io. Crates.io es donde las personas del ecosistema Rust publican sus proyectos open source para que otros los usen.

Despues de actualizar el registro, Cargo chequea nuestras dependencias (en [dependencies]) y las descarga de no tenerlas todavía. En este caso solo dijimos que queriamos depender en rand, y tambien obtuvimos una copia de libc. Esto es debido a que rand depende a su vez de libc para funcionar. Despues de descargar las dependencias, Cargo las compila, para despues compilar nuestro código.

Si ejecutamos cargo build, obtendremos una salida diferente:

$ cargo build

Asi es, no hay salida! Cargo sabe que nuestro proyecto ha sido construido, asi como todas sus dependencias, asi que no nay razón para hacer todo el proceso otra vez. Sin nada que hacer, simplemente termina la ejecucion. Si abrimos src/main.rs otra vez, hacemos un cambio trivial, salvamos los cambios, solamente veriamos una linea:

$ cargo build
    Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)

Entonces, le hemos dicho a Cargo que queriamos cualquier versión 0.3.x de rand, y este descargo la ultima versión para el momento de la escritura de este tutorial, v0.3.8. Pero que pasa cuando la siguiente versión v0.3.9 sea publicada con un importante bugfix? Si bien recibir bugfixes es importante, que pasa si 0.3.9 contiene una regresion que rompe nuestro codigo?

La respuesta a este problema es el archivo Cargo.lock, archivo que encontraras en tu directorio de proyecto. Cuando construyes tu proyecto por primera vez, cargo determina todas las versiones que coinciden con tus criterios y las escribe en el archivo Cargo.lock. Cuando construyes tu proyecto en el futuro, Cargo notara que un archivo Cargo.lock existe, y usara las versiones especificadas en el mismo, en vez de hacer todo el trabajo de determinar las versiones otra vez. Esto te permite tener una construcción reproducible de manera automatica. En otras palabras, nos quedaremos en 0.3.8 hasta que subamos de version de manera explicita, de igual manera lo hará la gente con la que hemos compartido nuestro código, gracias al archivo Cargo.lock.

Pero que pasa cuando queremos usar v0.3.9? Cargo posee otro comando, update, que se traduce en ‘ignora el bloqueo y determina todas las ultimas versiones que coincidan con lo que hemos especficado. De funcionar esto, escribe esas versiones al archivo de bloqueo Cargo.lock’. Pero, por defecto, Cargo solo buscara versiones mayores a 0.3.0 y menores a 0.4.0. Si queremos movernos a 0.4.x, necesitariamos actualizar el archivo Cargo.toml directamente. Cuando lo hacemos, la siguente vez que ejecutemos cargo build, Cargo actualizara el indice y re-evaluara nuestros requerimentos acerca de rand.

Hay mucho mas que decir acerca de Cargo y su ecosistema, pero por ahora, eso es todo lo que necesitamos saber. Cargo hace realmente fácil reusar bibliotecas, y los Rusteros tienden a escribir proyectos pequenos los cuales estan construidos por un conjunto de paquetes mas pequeños.

Hagamos uso ahora de rand. He aqui nuestro siguiente paso:

extern crate rand;

use std::io;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    println!("Por favor introduce tu corazonada.");

    let mut corazonada = String::new();

    io::stdin().read_line(&mut corazonada)
        .ok()
        .expect("Fallo al leer linea");

    println!("Tu corazonada fue: {}", corazonada);
}

La primera cosa que hemos hecho es cambiar la primera linea. Ahora dice extern crate rand. Debido a que declaramos rand en nuestra sección [dependencies], podemos usar extern crate para hacerle saber a Rust que estaremos haciendo uso de rand. Esto es equivalente a un use rand;, de manera que podamos hacer uso de lo que sea dentro del crate rand a traves del prefijo rand::.

Después, hemos agregado otra linea use: use rand::Rng. En unos momentos estaremos haciendo uso de un metodo, y esto requiere que Rng este disponible para que funcione. La idea basica es la siguiente: los metodos estan dentro de algo llamado ‘traits’ (Rasgos), y para que el metodo funcione necesita que el trait este disponible. Para mayores detalles dirigete a la sección Rasgos (Traits).

Hay dos lineas mas en el medio:

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

Hacemos uso de la función rand::thread_rng() para obtener una copia del generador de numeros aleatorios, el cual es local al hilo de ejecucion en el cual estamos. Debido a que hemos hecho disponible a rand::Rng a traves de use rand::Rng, este tiene un metodo gen_range() disponible. Este metodo acepta dos argumentos, y genera un numero aleatorio entre estos. Es inclusivo en el limite inferior, pero es exclusivo en el limite superior, por eso necesitamos 1 y 101 para obtener un numero entre uno y cien.

La segunda linea solo imprime el numero secreto. Esto es útil mietras desarrollamos nuestro programa, de manera tal que podamos probarlo. Estaremos eliminando esta linea para la version final. No es un juego si imprime la respuesta justo cuando lo inicias!

Trata de ejecutar el programa unas pocas veces:

$ cargo run
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/debug/adivinanzas`
Adivina el numero!
El numero secreto es: 7
Por favor introduce tu corazonada.
4
Tu corazonada fue: 4
$ cargo run
     Running `target/debug/adivinanzas`
Adivina el numero!
El numero secreto es: 83
Por favor introduce tu corazonada.
5
Tu corazonada fue: 5

Gradioso! A continuacion: comparemos nuestra adivinanza con el numero secreto.

Comparando adivinanzas

Ahora que tenemos entrada del usuario, comparemos la adivinanza con nuestro numero secreto. He aqui nuestro siguiente paso, aunque todavia no funciona completamente:

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    println!("Por favor introduce tu corazonada.");

    let mut corazonada = String::new();

    io::stdin().read_line(&mut corazonada)
        .ok()
        .expect("Fallo al leer linea");

    println!("Tu corazonada fue: {}", corazonada);

    match corazonada.cmp(&numero_secreto) {
        Ordering::Less    => println!("Muy pequeño!"),
        Ordering::Greater => println!("Muy grande!"),
        Ordering::Equal   => println!("Haz ganado!"),
    }
}

Algunas piezas acá. La primera es otro use. Hemos hecho disponible un tipo llamado std::cmp::Ordering. Despues, cinco nuevas lineas al fondo que lo usan:

match corazonada.cmp(&numero_secreto) {
    Ordering::Less    => println!("Muy pequeño!"),
    Ordering::Greater => println!("Muy grande!"),
    Ordering::Equal   => println!("Haz ganado!"),
}

El metodo cmp() puede ser llamado an cualquier cosa que pueda ser comparada, este toma una referencia a la cosa con la cual quieras comparar. Retorna el tipo Ordering que hicimos disponible anteriormente. Hemos usado una sentencia match para determinar exactamente que tipo de Ordering es. Ordering es un enum, abreviacion para ‘enumeration’, las cuales lucen de la siguiente manera:

enum Foo {
    Bar,
    Baz,
}

Con esta definición, cualquier cosa de tipo Foo puede ser bien sea un Foo::Bar o un Foo::Baz. Usamos el :: para indicar el espacio de nombres para una variante enum en particular.

La enum Ordering tiene tres posibles variantes: Less, Equal, and Greater (menor, igual y mayor respectivamente). La sentencia match toma un valor de un tipo, y te permite crear un ‘brazo’ para cada valor posible. Debido a que tenemos tres posibles tipos de Ordering, tenemos tres brazos:

match guess.cmp(&secret_number) {
    Ordering::Less    => println!("Muy pequeño!"),
    Ordering::Greater => println!("Muy grande!"),
    Ordering::Equal   => println!("Haz ganado!"),
}

Si es Less, imprimimos Too small!, si es Greater, Too big!, y si es Equal, Haz ganado!. match es realmente util, y es usado con fercuencia en Rust.

Anteriormente mencione que todavia no funciona. Pongamoslo a prueba:

$ cargo build
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
src/main.rs:28:21: 28:35 error: mismatched types:
 expected `&collections::string::String`,
    found `&_`
(expected struct `collections::string::String`,
    found integral variable) [E0308]
src/main.rs:28     match corazonada.cmp(&numero_secreto) {
                                   ^~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `adivinanzas`.

Oops! Un gran error. Lo principal en el es que tenemos ‘tipos incompatibles’ (‘mismatched types’). Rust posee un fuerte, sistema de tipos estatico. Sin embargo, también tiene inferencia de tipos. Cuando escribimos let corazonada = String::new(), Rust fue capaz de inferir que corazonada debia ser un String, y por ello no nos hizo escribir el tipo. Con nuestro numero_secreto, hay un numero de tipos que pueden tener un valor entre uno y cien: i32, un numero de treinta y dos bits, u32, un numero sin signo de treinta y dos bits, o i64 un numero de sesenta y cuatro bits u otros. Hasta ahora, eso no ha importado, debido a que Rust por defecto usa i32. Sin embargo, en este caso, Rust no sabe como comparar corazonada con numero_secreto. Ambos necesitan ser del mismo tipo. A la final, queremos convertir el String que leimos como entrada en un tipo real de numero, para efectos de la comparación. Podemos hacer eso con tres lineas mas. He aqui nuestro nuevo programa:

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    println!("Por favor introduce tu corazonada.");

    let mut corazonada = String::new();

    io::stdin().read_line(&mut corazonada)
        .ok()
        .expect("Fallo al leer linea");

    let corazonada: u32 = corazonada.trim().parse()
        .ok()
        .expect("Por favor introduce un numero!");

    println!("Tu corazonada fue: {}", corazonada);

    match corazonada.cmp(&numero_secreto) {
        Ordering::Less    => println!("Muy pequeño!"),
        Ordering::Greater => println!("Muy grande!"),
        Ordering::Equal   => println!("Haz ganado!"),
    }
}

Las tres nuevas lineas:

    let corazonada: u32 = corazonada.trim().parse()
        .ok()
        .expect("Por favor introduce un numero!");

Espera un momento, pensé que ya teniamos una corazonada? La tenemos, pero Rust nos permite sobreescribir (‘shadow’) la corazonada previa con una nueva. Esto es usado con frecuencia en esta misma situación, en donde corazonada es un String, pero queremos convertirlo a un u32. Este shadowing nos permite reusar el nombre corazonada en vez de forzarnos a idear dos nombres únicos como corazonada_str y corazonada, u otros.

Estamos asociando corazonada a una expresión que luce como algo que escribimos anteriormente:

guess.trim().parse()

Seguido por una invocación a ok().expect(). Aquí corazonada hace referencia a la vieja versión, la que era un String que contenía nuestra entrada de usuario en ella. El metodo trim() en los Strings elimina cualquier espacio en blanco al principio y al final de nuestras cadenas de caracteres. Esto es importante, debido a que tuvimos que presionar la tecla ‘retorno’ para satisfacer a read_line(). Esto significa que si escribimos 5 y presionamos ‘retorno’ corazonada luce como así: 5\n. El \n representa ‘nueva linea’ (‘newline’), la tecla enter. trim() se deshace de esto, dejando nuestra cadena de caracteres solo con el 5. El metodo parse() en las cadenas caracteres parsea una cadena de caracteres en algún tipo de numero. Debido a que puede parsear una variedad de numeros, debemos darle a Rust una pista del tipo exacto de numero que deseamos. De ahí la parte let corazonada: u32. Los dos puntos (:) despues de corazonada le dicen a Rust que vamos a anotar el tipo. u32 es un entero sin signo de treinta y dos bits. Rust posee una variedad de tipos numero integrados, pero nosotros hemos escojido u32. Es una buena opción por defecto para un numero positivo pequeño.

Al igual que read_line(), nuestra llamada a parse() podria causar un error. Que tal si nuestra cadena de caracteres contiene Aߑ���? No habría forma de convertir eso en un numero. Es por ello que haremos lo mismo que hicimos con read_line(): usar los metodos ok() y expect() para terminar abruptamente si hay algun error.

Probemos nuestro programa!

$ cargo run
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/adivinanzas`
Adivina el numero!
El numero secreto es: 58
Por favor introduce tu corazonada.
  76
Tu corazonada fue: 76
Muy grande!

Excelente! Puedes ver que incluso he agregado espacios antes de mi intento, y aun así el programa determino que intente 76. Ejecuta el programa unas pocas veces, y verifica que adivinar el numero funciona, asi como intentar un numero muy pequeno.

Ahora tenemos la mayoria del juego funcionando, pero solo podemos intentar adivinar una vez. Tratemos de cambiar eso agregando ciclos!

Iteración

La palabra clave loop nos proporciona un ciclo infinito. Agreguemosla:

Adivina el numero! El numero secreto es: 58 Por favor introduce tu adivinanza. 76 Tu corazonada fue: 76 Muy grande!

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    loop {
        println!("Por favor introduce tu corazonada.");

        let mut corazonada = String::new();

        io::stdin().read_line(&mut corazonada)
            .ok()
            .expect("Fallo al leer linea");

        let corazonada: u32 = corazonada.trim().parse()
            .ok()
            .expect("Por favor introduce un numero!");

        println!("Haz corazonada: {}", corazonada);

        match corazonada.cmp(&numero_secreto) {
            Ordering::Less    => println!("Muy pequeño!"),
            Ordering::Greater => println!("Muy grande!"),
            Ordering::Equal   => println!("Haz ganado!"),
        }
    }
}

Pruebalo. Pero espera, no acabamos de agregar un ciclo infinito? Sip. Recuerdas nuestra discusión acerca de parse()? Si damos una respuesta no numérica, retornaremos (return) y finalizaremos la ejecución. Observa:

$ cargo run
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/adivinanzas`
Adivina el numero!
El numero secreto es: 59
Por favor introduce tu corazonada.
45
Tu corazonada fue: 45
Muy pequeño!
Por favor introduce tu corazonada.
60
Tu corazonada fue: 60
Muy grande!
Por favor introduce tu corazonada.
59
Tu corazonada fue: 59
Haz ganado!
Por favor introduce tu corazonada.
quit
thread '<main>' panicked at 'Please type a number!'

Ja! quit en efecto termina la ejecución. Asi como cualquier otra entrada que no sea un numero. Bueno, esto es suboptimo por decir lo menos. Primero salgamos cuando ganemos:

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    loop {
        println!("Por favor introduce tu corazonada.");

        let mut corazonada = String::new();

        io::stdin().read_line(&mut corazonada)
            .ok()
            .expect("Fallo al leer linea");

        let corazonada: u32 = corazonada.trim().parse()
            .ok()
            .expect("Por favor introduce un numero!");

        println!("Tu corazonada fue: {}", corazonada);

        match corazonada.cmp(&numero_secreto) {
            Ordering::Less    => println!("Muy pequeño!"),
            Ordering::Greater => println!("Muy grande!"),
            Ordering::Equal   => {
                println!("Haz ganado!");
                break;
            }
        }
    }
}

Al agregar la linea break despues del "Haz ganado!", romperemos el ciclo cuando ganemos. Salir del ciclo también significa salir del programa, debido a que es la ultima cosa en main(). Solo nos queda una sola mejora por hacer: cuando alguien introduzca un valor no numérico, no queremos terminar la ejecución, queremos simplemente ignorarlo. Podemos hacerlo de la siguiente manera:

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    println!("El numero secreto es: {}", numero_secreto);

    loop {
        println!("Por favor introduce tu corazonada.");

        let mut corazonada = String::new();

        io::stdin().read_line(&mut corazonada)
            .ok()
            .expect("Fallo al leer linea");

        let corazonada: u32 = match corazonada.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("Tu corazonada fue: {}", corazonada);

        match corazonada.cmp(&numero_secreto) {
            Ordering::Less    => println!("Muy pequeño!"),
            Ordering::Greater => println!("Muy grande!"),
            Ordering::Equal   => {
                println!("Haz ganado!");
                break;
            }
        }
    }
}

Estas son las lineas que han cambiado:

let corazonada: u32 = match corazonada.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

Es asi como pasamos de ‘terminar abruptamente en un error’ a ‘efectivamente manejar el error’, a través del cambio de ok().expect() a una sentencia match. El Result retornado por parse() es un enum justo como Ordering, pero en este caso cada variante tiene data asociada: Ok es exito, y Err es una falla. Cada uno contiene mas información: el entero parseado en el caso exitoso, o un tipo de error. En este caso hacemos match enOk(num), el cual asigna el valor interno del Ok a el nombre num, y seguidamente retorna en el lado derecho. En el caso de Err, no nos importa que tipo de error es, es por ello que usamos _ en lugar de un nombre. Esto ignora el error y continue nos mueve a la siguiente iteración del ciclo (loop).

Ahora deberiamos estar bien! Probemos:

$ cargo run
   Compiling adivinanzas v0.1.0 (file:///home/tu/proyectos/adivinanzas)
     Running `target/adivinanzas`
Adivina el numero!
El numero secreto es: 61
Por favor introduce tu corazonada.
10
Tu corazonada fue: 10
Muy pequeño!
Por favor introduce tu corazonada.
99
Tu corazonada fue: 99
Muy pequeño!
Por favor introduce tu corazonada.
foo
Por favor introduce tu corazonada.
61
Tu corazonada fue: 61
Haz ganado!

Genial! Con una ultima mejora, finalizamos el juego de las advinanzas. Te imaginas cual es? Es correcto, no queremos imprimir el numero secreto. Era bueno para las pruebas, pero arruina nuestro juego. He aqui nuestro código fuente final:

extern crate rand;

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
    println!("Adivina el numero!");

    let numero_secreto = rand::thread_rng().gen_range(1, 101);

    loop {
        println!("Por favor introduce tu corazonada.");

        let mut corazonada = String::new();

        io::stdin().read_line(&mut corazonada)
            .ok()
            .expect("Fallo al leer linea");

        let corazonada: u32 = match corazonada.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("Tu corazonada fue: {}", corazonada);

        match corazonada.cmp(&numero_secreto) {
            Ordering::Less    => println!("Muy pequeño!"),
            Ordering::Greater => println!("Muy grande!"),
            Ordering::Equal   => {
                println!("Haz ganado!");
                break;
            }
        }
    }
}

Completado!

En este punto, has terminado satisfactoriamente el juego de las adivinanza! Felicitaciones!

Este primer proyecto te enseno un montón: let, match, metodos, funciones asociadas, usar crates externos, y mas. Nuestro siguiente proyecto demostrara aun mas.