Hello mundo en Rust – Aprende Rust 2/x

hello mundo en rust

Ya en el primer post vimos cómo instalar Rust y Cargo, y un mini ejemplo de nuestro Hola Mundo. Ya en este haremos un ejercicio que introduce algunos conceptos que iremos afinando y será nuestro Hello Mundo en Rust. Contiene bastantes claves que nos darán un sólido conocimiento del cómo funciona Rust.

Manos a la obra. El programa debe generar un número entre 1 y 100, y el usuario capturará un número. El programa debe indicar si una vez capturado este número es mayor o menor en un loop hasta que sea correcto.

Primero crearemos un proyecto con cargo.

cargo new guessing_game
cd guessing_game

Luego añadimos el siguiente código al archivo src/main.rs:

use std::io;

fn main() {
    println!("Guess the number!");
    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

Aquí ya vemos algunas cosas simpáticas, como mut antes del nombre de la variable o métodos encadenados (method chaining).

Rust (como cualquier otro lenguaje) incluye en cada programa, sin tener que escribir en el código, algunas funcione básicas, esto se llama prelude. Sin embargo, hay otro set de funciones que deben incluirse explícitamente para que otras funciones puedan ejecutarse, por ejemplo, la siguiente línea:

use std::io

std es la librería estándar de Rust. Si no la incluyes en este ejemplo, deberías ver cómo la línea de io::stdin() se pone roja en el editor y nos indica que no puede llamar porque falta el crate (recordemos que Rust llama así a sus paquetes) o módulo.

Guardar valores en las variables

Para guardar el número capturado por el usuario usamos la siguiente línea:

let mut guess = String::new();

Usamos let para declarar la variable y mut para indicarle que la variable puede cambiar. ¿Y esto por qué? Pues en Rust las variables son inmutables, es decir, una vez asignado el valor no va a cambiar (algo como final en Dart), a menos que le indiquemos con mut que en algún momento puede hacerlo. Ejemplos:

let edad = 33; // inmutable, NO cambia
let mut edad = 33; // mutable, SI cambia

Como vemos, guess es una variable de tipo String.Al declarar la variable de esta manera le decimos a Rust que vamos a necesitar una secuencia de caracteres UTF-8 vacía. Aunque se le puede establecer un valor de inicio:

let mut mi_nombre = String::new("Manuel Hernández");
  • String es el tipo de dato dentro de la librería estándar.
  • ::new indica que vamos a llamar a una función de String. Imagina a String como la clase y a new como la función o constructor de la clase.

Capturar información

Al principio del programa agregamos la línea para incluir el módulo io de la librería std. Pues ahora vamos a llamar a la función stdin del módulo io.

io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");
  • stdin regresa una instancia de std::io::Stdin.
  • read_line permite la captura de un dato y le pasa ese valor a guess. Como declaramos como mut a guess, entonces le pasamos el valor con &mut, que básicamente es la referencia del valor (como si fuese el puntero).
  • expected nos da el mensaje de error que va a mostrar si hay un error durante la captura.
  • read_line pone el valor del usuario en la variable, pero también regresa otro valor de tipo Result que pueden ser Ok o Err (los usaremos más adelante), que indican si el proceso se pudo hacer o no. Si no agregamos el expect el programa compila, pero nos advierte que el programa se puede ir al carajo si no hacemos el “catch” de ese error.

Imprimir por pantalla

En nuestro primer hola mundo vimos la macro println!, en este ejemplo tenemos la línea:

println!("You guessed: {guess}");

Aquí lo que está entre las llaves se va a interpretar y va a imprimir el valor.

let x = 5;
let y = 10;

println!("x = {x} and y + 2 = {}", y + 2);

Acá vemos otra manera de hacerlo, que el segundo argumento de println! sería como enviar el valor a printf en otros lenguajes.

Generar un número aleatorio

Como dijimos al principio, el programa generará un número entre 1 y 100 de manera aleatoria, para ello usaremos el crate llamado rand y ahora le daremos uso al Cargo.toml.

En la sección dependencies del archivo Cargo.toml:

[dependencies]
rand = "0.8.5"

Ahora ejecutamos:

cargo build

Con esto nos descargará lo necesario que el crate indique para funcionar correctamente.

NOTA: puedes intentar cambiar la versión, por ejemplo a la “0.9.0”, pero al intentar hacer el build no podrá, ya que la versión no existe en el repositorio de crates.

Ahora modificamos el código de src/main.rs:

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

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

De la misma manera en que agregamos la librería estándar io:std, ahora agregaremos el paquete rand.

use rand::Rng;

Acá le decimos a Rust que vamos a usar Rng del nuevo crate. Ojo, no basta con agregarlo a nuestra lista de dependencias, tenemos que decirle explícitamente que vamos a usarlo.

Miremos la siguiente línea:

let secret_number = rand::thread_rng().gen_range(1..=100);

Del espacio rand llamos a la función thread_rng que nos “aparta” un lugar para ejecutarse, luego llamamos a gen_range que toma el rango que le decimos y nos devuelve el número entre 1 y 100, que lo indicamos enviando 1..=100. Esta expresión es como decir “desde 1 hasta que sea igual a 100” o los límites bajo y alto.

Comparar nuestro número con el aleatorio

Primero agregamos en la sección de use un tipo llamado std::cmp::Ordering que contiene tres variantes que devuelve: Less, Greater y Equal.

use std::cmp::Ordering;

Luego un poco de código:

// …
println!("You guessed: {guess}");

match guess.cmp(&secret_number) {
    Ordering::Less => println!("Too small!"),
    Ordering::Greater => println!("Too big!"),
    Ordering::Equal => println!("You win!"),
}

Acá el método cmp compara dos valores: guess (al que le “attachamos” el método) y el secret_number.

Ahora ¿qué pasa con match? Como cmp regresa alguna de las variantes Less, Greater o Equal entonces debemos decidir qué hacer ante las diferentes situaciones. Podemos decir que match es un pattern que dada una serie de valores, elige el camino que coincide con ese patrón.

Así que, si el usuario captura 50 y el número aleatorio es 2, el método cmp regresará la variante Ordering::Greater porque 50 > 2, o sea 50 es mayor que 2. Lo mismo pasa con los otros escenarios Ordering::Less y Ordering::Equal.

Tipos de datos de Rust

Si conoces algo ya de programación te estará comiendo la duda sobre los tipos de datos. String no es Int. Esto no es PHP, Rust es un lenguaje tipado, lo que nos obliga a que cada variable tenga un tipo de dato. En el ejemplo, aunque no hemos indicado el tipo, Rust infiere cuál tipo de dato será de acuerdo a lo que String::new() o thread_rng().gen_range(1..=100) regresen. Pero esto lo veremos más adelante.

Agregamos este código:

//…
let mut guess = String::new();

io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");

let guess: u32 = guess.trim().parse().expect("Please type a number!");

println!("You guessed: {guess}");

//…

Tenemos esta línea nueva let guess: u32.... ¿Y esto qué eeeeeees? Ya habíamos declarado la variable guess ¿y ahora lo vuelvo a hacer? Rust tiene algo que se llama Shadowing que nos permite reusar una variable en lugar de crear otra. Finalmente, la captura de un dato solo es eso, sin embargo, el valor que vayamos a usar después de capturarlo siempre será tratado como entero u32. Por ahora solo diremos que es un Int o Integer.

Esta nueva línea hace lo siguiente:

  1. Toma la variable original guest y le quita los espacios con trim, al principio y al final. Como nota, cuando el usuario captura supongamos el número 10 en realidad se está agregando el salto de línea al final, entonces es 10\n. trim ayuda a “sanitizar” y que solo quede el número sin estorbos.
  2. parse convierte de String a u32.
  3. expect como lo habíamos visto antes, captura el error en caso de que no se pueda convertir porque el ocurrent usuario le envió un emoji :P.

Si hacemos cargo build debería compilar sin errores o warnings.

Ciclos en Rust

Ahora ya tenemos la forma de capturar, convertir y comparar, solo que por el momento, el usuario si no adivina al principio, el programa se termina. Por ello ahora veremos los ciclos o loops.

Entonces agregamos lo siguiente:

//…
println!("The secret number is: {secret_number}");
loop {
println!("Please input your guess.");

    //...

    match guess.cmp(&secret_number) { 
        Ordering::Less => println!("Too small!"), 
        Ordering::Greater => println!("Too big!"), 
        Ordering::Equal => { 
            println!("You win!"); 
            break; 
        } 
    }
}

Aquí vemos que loop se encarga del código entre las llaves y se rompe con break cuando la comparación dcon cmp regresa el enum Ordering::Equal.

Dentro de los ciclos en Rust también existe la keyword continue. Lo que hace es saltar el código restante del bloque loop y reiniciar la iteración. ¿Cómo se usa? Pues imagina que estás dentro del ciclo, adivinando el número, entonces se te va la mano y pegas un emoji. El programa te lanza un mensaje, pero de cualquier manera debemos enviar un número para que el programa continue la ejecución.

Para gestionar esto, podemos agregar lo siguiente:

//…
io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read line");

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

println!("You guessed: {guess}");

//…

Si parse regresa Ok, quiere decir que sí es un número válido y pudo hacer el parse correctamente, si no, en caso de ser Err nos saltamos todo y comenzamos la iteración para capturar de nuevo el número.

Al final el archivo queda de esta manera:

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

fn main() {
println!("Guess the number!");

let secret_number = rand::thread_rng().gen_range(1..=100);

loop {
    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

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

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => {
            println!("You win!");
            break;
        }
    }
}
}

¡Muchas cositas eh para ser un Hello Mundo en Rust!

Gracias por leer.

Este material fue sacado de la documentación oficial.


Posted

in

, , , ,

by