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 deString
. Imagina aString
como la clase y anew
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 destd::io::Stdin
.read_line
permite la captura de un dato y le pasa ese valor aguess
. Como declaramos comomut
aguess
, 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 tipoResult
que pueden serOk
oErr
(los usaremos más adelante), que indican si el proceso se pudo hacer o no. Si no agregamos elexpect
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:
- Toma la variable original
guest
y le quita los espacios contrim
, al principio y al final. Como nota, cuando el usuario captura supongamos el número10
en realidad se está agregando el salto de línea al final, entonces es10\n
.trim
ayuda a “sanitizar” y que solo quede el número sin estorbos. parse
convierte deString
au32
.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.