Crates externas

Un crate (Cajón -librería o biblioteca-) externa es aquella que se ha desarrollado fuera del proyecto en desarrollo.

Para esta sección casi se necesita ya tener instalado Rust, pero aún puede realizarse usando solo Playground. Se va a aprender cómo importar crates que otras personas hayan escrito para utilizarlas en un proyecto de Rust. Esto es importante en Rust por dos motivos:

  • Es muy fácil importar otras crates.
  • La librería de Rust estándar es muy pequeña.

Esto significa que es muy normal tener que importar crates externas para funciones aparentemente básicas. La idea es que si es fácil usar librerías (crates) externas, se puede elegir la mejor existente.

En este libro solo aparecen algunas de las crates más populares. Aquellas que todo el mundo que usa Rust conoce.

Para comentar a aprender las crates externas, se comenzará con una de las más comunes: rand.

rand

Hasta el momento, no se han utilizado números aleatorios. No se encuentran en la librería estándar. Si se ha instalado Rust en el ordenador y se ha iniciado un proyecto con cargo init, existe un fichero Cargo.toml en el directorio del proyecto. En este fichero reside, entre otra, la información de los crates que esté utilizando el proyecto. Un fichero Cargo.toml tiene la siguiente apariencia:

[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Para añadir el crate (la librería) rand, se puede buscar en crates.io, que es el almacén online de todas las crates publicadas: https://crates.io/crates/rand. En la página anterior, dedicada a rand se observa a la derecha una explicación que indica cómo añadir este crate al fichero Cargo.toml: rand = "0.7.8" (NT: el número de versión puede ser mayor ya que se va actualizando periódicamente). Esta línea, se debe añadir bajo el apartado [dependencies] del fichero Cargo.toml:

[package]
name = "rust_book"
version = "0.1.0"
authors = ["David MacLeod"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.7.3"

Con esta línea añadida, Cargo se encarga de todo. A partir de este momento, se puede escribir código como el de ejemplo de este crate en la documentación de rand. Para llegar a la página de documentación de una librería se puede pulsar el enlace a docs de la página de crates.io.

Esto es suficiente por ahora sobre Cargo. Aún se está usando Playground. Afortunadamente, Playground tiene las 100 crates más usada precargadas. Por ello, aún no es necesario escribirlas en Cargo.toml. En Playground se debe pensar que existe una lista de 100 crates como:

[dependencies]
rand = "0.7.3"
some_other_crate = "0.1.0"
another_nice_crate = "1.7"

Esto significa que para usar rand en Playground basta con escribirlo en el código así:

extern crate rand; // Esto significa que se use toda la librería rand.
          // En un proyecto propio en un ordenador no se puede solo escribir esto,
          // es necesario haber añadido primero la librería en Cargo.toml.

fn main() {
    for _ in 0..5 {
        let random_u16 = rand::random::<u16>();
        print!("{} ", random_u16);
    }
}

Este código imprime un número u16 diferente cada vez, como 42266 52873 56528 46927 6867.

Las funciones principales de rand son random y thread_rng (rng significa "random number generator" -generador de números aleatorios-)-. La propia función random es un atajo a thread_rng().gen(). Por lo que es esta última thread_rng quien en realidad hace casi todo el trabajo.

A continuación, se muestra un ejemplo de números del 1 al 10. Para obtener estos números se utiliza .get_range() entre 1 y 11.

extern crate rand;
use rand::{thread_rng, Rng}; // O simplemente rand::*; con pereza

fn main() {
    let mut number_maker = thread_rng();
    for _ in 0..5 {
        print!("{} ", number_maker.gen_range(1..11));
    }
}

Que imprime cosas como 7 2 4 8 6.

Los números aleatorios permiten cosas divertidas, como crear los personajes de un juego. Con rand y algunas otras herramientas se puede simular un dado d6 (con seis caras: 1, 2, 3, 4, 5, 6). En este juego el personaje tiene seis tipos de características. Para establecer cada característica, se podría lanzar tres veces el dado de seis caras y sumar el resultado. De esta forma, cada característica podría tener un valor entre 3 y 18.

En ocasiones puede no ser justo que un personaje tenga algo tan bajo como 3 o 4. Si la fuerza es 3 no se puede transportar nada, por ejemplo. Para evitar esto, se puede usar un método que lanza el dado d6 cuatro veces y descarta el valor más bajo. Así si el resultado es 3, 3, 1, 6, se conservan los siguientes 3, 3, 6, y el resultado es 12. Este será el método que se desarrollará en este ejemplo:

extern crate rand;
use rand::{thread_rng, Rng}; // O rand::*; si se está perezoso
use std::fmt; // Se va a impl Display para el personaje


struct Character {
    strength: u8,
    dexterity: u8,    // Esto signfica "velocidad del cuerpo"
    constitution: u8, // Esto signfica "salud"
    intelligence: u8,
    wisdom: u8,
    charisma: u8, // Esto signfica "popularidad con la gente"
}

fn three_die_six() -> u8 { // Un "die" es lo que lanzas para obtener el número
    let mut generator = thread_rng(); // Crea un generador de números aleatorios
    let mut stat = 0; // Estadísticas totales
    for _ in 0..3 {
        stat += generator.gen_range(1..=6); // Suma el resultado cada vez
    }
    stat // Devuelve el total
}

fn four_die_six() -> u8 {
    let mut generator = thread_rng();
    let mut results = vec![]; // Primero guarda los números en un vec
    for _ in 0..4 {
        results.push(generator.gen_range(1..=6));
    }
    results.sort(); // Con esto, ordena [4, 3, 2, 6] en [2, 3, 4, 6]
    results.remove(0); // Ahora sería [3, 4, 6]
    results.iter().sum() // Devuelve el resultado
}

enum Dice {
    Three,
    Four
}

impl Character {
    fn new(dice: Dice) -> Self { // Crea el personaje con tres o cuatro tiradas
        match dice {
            Dice::Three => Self {
                strength: three_die_six(),
                dexterity: three_die_six(),
                constitution: three_die_six(),
                intelligence: three_die_six(),
                wisdom: three_die_six(),
                charisma: three_die_six(),
            },
            Dice::Four => Self {
                strength: four_die_six(),
                dexterity: four_die_six(),
                constitution: four_die_six(),
                intelligence: four_die_six(),
                wisdom: four_die_six(),
                charisma: four_die_six(),
            },
        }
    }
    fn display(&self) { // Esto funciona gracias a implementar Display a continuación
        println!("{}", self);
        println!();
    }
}

impl fmt::Display for Character { // Basta con seguir https://doc.rust-lang.org/std/fmt/trait.Display.html y cambiarlo un poco
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Tu personaje tiene las siguientes características:
fuerza: {}
destreza: {}
constitución: {}
inteligencia: {}
sabiduría: {}
carisma: {}",
            self.strength,
            self.dexterity,
            self.constitution,
            self.intelligence,
            self.wisdom,
            self.charisma
        )
    }
}



fn main() {
    let weak_billy = Character::new(Dice::Three);
    let strong_billy = Character::new(Dice::Four);
    weak_billy.display();
    strong_billy.display();
}

Normalmente el personaje generado con cuatro tiradas de dado tendrá mejores características.

rayon

rayon es una librería muy popular que permite acelerar el código Rust. Es popular porque crea hilos sin necesidad de hacer cosas como thread::spawn. Es efectiva y fácil de usar. Por ejemplo:

  • .iter(), .iter_mut(), .into_iter() en esta librería se escriben así:
  • .par_iter(), .par_iter_mut(), .par_into_iter() y permite ejecutar las operaciones en paralelo.

Así sucede con otros métodos: .chars() es .par_chars() y así con todos.

A continuación, se muestra un ejemplo de un código muy simple que obliga a trabajar mucho al ordenador:

fn main() {
    let mut my_vec = vec![0; 200_000];
    my_vec.iter_mut().enumerate().for_each(|(index, number)| *number+=index+1);
    println!("{:?}", &my_vec[5000..5005]);
}

Crea un vector de 200.000 elementos: cada uno de ellos vale 0. Luego llama a .enumerate() para obtener el índice de cada uno de ellos y cambia el valor 0 por el número de índice. Como es un vector muy largo, se imprimen solo algunos valores, del 5000, al 5004. Esto es rápido en Rust, pero con rayon es aún más rápido y el código es casi igual:

extern crate rayon;
use rayon::prelude::*; // Import rayon

fn main() {
    let mut my_vec = vec![0; 200_000];
    my_vec.par_iter_mut().enumerate().for_each(|(index, number)| *number+=index+1); // add par_ to iter_mut
    println!("{:?}", &my_vec[5000..5005]);
}

Esto es todo. Con rayon se dispone de un conjunto de métodos muy amplio que se ajustan a cada necesidad. En general es tan sencillo como añadir par_ al nombre de la función...

serde

Esta librería se usa de forma habitual para convertir desde y hacia formatos como JSON, YAML, etc. Normalmente se crea un struct dos atributos específicos (enlace a la documentación de la librería) como en el siguiente código:



#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}
}

Los rasgos Serialize y Deserialize hacen fácil la conversión (de ellos viene el nombre serde). Añaden métodos para convertir desde y hacia los diferentes formatos disponibles.

regex

La librería regex permite buscar en un texto mediante expresiones regulares. Con ella se pueden encontrar referencias a cosas parecidas, pero diferentes, como color, colores, colours en una sola búsqueda. Las expresiones regulares son un un lenguaje completo que es necesario conocer para poder usar esta librería.

chrono

chrono es la librería principal que se utiliza para disponer de funcionalidad avanzada sobre el tiempo. Lo que no esté disponible en la librería estándar, se puede buscar aquí.