Vectores

Puedes ver este capítulo en Youtube en inglés

Del mismo modo que se dispone de &str y String, se dispone de arrays y vectores. Los arrays son más rápidos, pero tienen menos funcionalidad, y los vectores son más lentos, pero tienen más funcionalidad (Rust siempre es muy rápido, solo que los vectores no son tan rápidos como los arrays). El tipo es Vec y, por lo tanto, se le puede llamar como "vec".

Existen principalemente dos formas de declarar un vector. Una es igual a como se crea un String, mediante el uso de new:

fn main() {
    let name1 = String::from("Windy");
    let name2 = String::from("Gomesy");

    let mut my_vec = Vec::new();
    // Si se compilara este programa hasta aquí, el compilador dará un error.
    // ya que no conoce el tipo de datos del vec.

    my_vec.push(name1); // Ahora sí lo conoce, es un Vec<String>
    my_vec.push(name2);
}

Los Vec siempre contienen valores y para eso sirven <> (los paréntesis angulares). Un Vec<String> es un vector que contiene elementos String. Algunos otros ejemplos son:

  • Vec<(i32, i32)> es un vector en el que cada elemento de contenido es una tupla (i32, i32).
  • Vec<Vec<String>> es un vector en el que cada elemento es otro vector de String. Por ejemplo, se puede pensar en almacenar el texto de un libro como un Vec<String>. Para almacenar varios libros haría falta crear una lista de elementos del tipo anterior y esto se puede hacer en otro Vec que contiene Vec<String>. Por lo tanto, el tipo resultante sería así Vec<Vec<String>>.

En lugar de usar .push() para llegar a deducir el tipo de elementos que contiene un vector, se puede declarar el tipo:

fn main() {
    let mut my_vec: Vec<String> = Vec::new(); // El compilador conoce el tipo
                                              // Por eso no hay error
}

Como se observa, todos los elementos de un vector tienen que tener un mismo tipo.

Otra forma sencilla de crear un vector es usando la macro vec!, cuya sintaxis recuerda a la declaración de un array.

fn main() {
    let mut my_vec = vec![8, 10, 10];
}

El tipo de los elementos, en este ejemplo, es Vec<i32>. Un vector de enteros.

Se pueden obtener secciones de un vector, igual que como se hace para un array.

fn main() {
    let vec_of_ten = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    // Todo el código es idéntico, salvo que se añade vec!.
    let three_to_five = &vec_of_ten[2..5];
    let start_at_two = &vec_of_ten[1..];
    let end_at_five = &vec_of_ten[..5];
    let everything = &vec_of_ten[..];

    println!("Three to five: {:?},
start at two: {:?}
end at five: {:?}
everything: {:?}", three_to_five, start_at_two, end_at_five, everything);
}

Puesto que un vector es más lento que un array, se pueden usar diversos métodos para hacerlo más rápido. Un vector tiene una capacidad de espacio asignada, que permite que al ir insertando nuevos elementos en el vector, se haga rápidamente. Cada vez que se hace esto, el vector se acerca al límite de su capacidad. Cuando esta se supera, Rust crea un nuevo espacio del doble del tamaño actual y copia todos los elementos al nuevo espacio. Esto se denomina relocalización. Se puede usar el método .capacity()para ver la capacidad de un vector según se le van añadiendo elementos.

fn main() {
    let mut num_vec = Vec::new();
    println!("{}", num_vec.capacity()); // 0 elementos: immprime 0
    num_vec.push('a'); // añade un carácter
    println!("{}", num_vec.capacity()); // 1 elemento: imprime 4. Vecs con 1 elemento siempre se inician con una capacidad de 4
    num_vec.push('a'); // añade uno más
    num_vec.push('a'); // añade uno más
    num_vec.push('a'); // añade uno más
    println!("{}", num_vec.capacity()); // 4 elementos: aún 4 de capacidad.
    num_vec.push('a'); // añade uno más
    println!("{}", num_vec.capacity()); // imprime 8. Son 5 elementos, pero ha doblado la capacidad de 4 a 8 para hacer espacio.
}

Esto imprime:

0
4
4
8

Así que este vector ha sufrido dos relocalizaciones: de 0 a 4 y de 4 a 8. Para que fuese más rápido se puede iniciar así:

fn main() {
    let mut num_vec = Vec::with_capacity(8); // Se crea con una capacidad inicial de 8
    num_vec.push('a'); // se añade un carácter
    println!("{}", num_vec.capacity()); // imprime 8
    num_vec.push('a'); // añade uno más
    println!("{}", num_vec.capacity()); // imprime 8
    num_vec.push('a'); // añade uno más
    println!("{}", num_vec.capacity()); // imprime 8.
    num_vec.push('a'); // añade uno más
    num_vec.push('a'); // añade uno más
    // Ahora hay 5 elementos
    println!("{}", num_vec.capacity()); // Aún 8
}

Este vector no ha sufrido ninguna relocalización, lo que es mejor. Por eso, si se conoce a priori el número de elementos que se necesitará, se puede inicializar el vector con Vec::with_capacity() para que funcione más rápido.

En el caso de las &str se podía utilizar .into() para convertirlo en una String. Igualmente, se puede convertir un array en un vector con la misma función.

fn main() {
    let my_vec: Vec<u8> = [1, 2, 3].into();
    let my_vec2: Vec<_> = [9, 0, 10].into(); // Vec<_> significa "elige el tipo del Vector por mí"
                                             // Rust elegirá Vec<i32>
}