Tipos compuestos y colecciones en Rust

Escrito por una persona el y actualizado el

Tanto los tipos compuestos como las colecciones son tipos de datos que agrupan múltiples valores (de un mismo o diferente tipo). La diferencia principal entre ambos es que los tipos compuestos se almacenan en la pila y tienen un tamaño conocido en tiempo de compilación mientras que las colecciones se almacenan en el montón y su tamaño puede variar en tiempo de ejecución.

Tipos compuestos

Tupla

Una tupla almacena valores que pueden ser de diferente tipo. A cada valor se le llama elemento.

Una tupla se inicializa de la siguiente manera:

let tupla = ('A', "Alfa", 10i32, true);

Una tupla tiene un tamaño fijo y no puede aumentar o disminuir a lo largo de la ejecución del programa.

Para acceder a los valores de una tupla se usa un índice que indica su posición dentro de la tupla. El primer elemento de la tupla tiene el índice 0, el segundo el 1 y así sucesivamente.

println!("{:?}", tupla.0); // 'A'
println!("{:?}", tupla.1); // "Alfa"
println!("{:?}", tupla.2); // 10
println!("{:?}", tupla.3); // true

Tratar de acceder a un valor mediante un índice igual o mayor que el tamaño de la tupla dará un error en tiempo de compilación.

println!("{:?}", tupla.4);

// Al compilar este código obtenemos el siguiente error:

error[E0609]: no field `4` on type `(char, &str, i32, bool)`
 --> src/main.rs:8:28
  |
  |     println!("{:?}", tupla.4);
  |      

Es posible modificar el valor de un elemento.

tupla.2 = 6i32;
println!("{:?}", tupla.2); // Mostrará 6

Pero ha de ser por un valor del mismo tipo. La inicialización de una tupla marca el tipo de cada elemento a lo largo de todo el código. Y éste es evaluado en tiempo de compilación.

tupla.2 = "Beta";
println!("{:?}", tupla.2);

error[E0308]: mismatched types
 --> src/main.rs:8:15
  |
  |     tupla.2 = "Beta";
  |     -------   ^^^^^^ expected `i32`, found `&str`
  |     |
  |     expected due to the type of this binding

Matriz

Una matriz almacena valores de un mismo tipo. A cada valor se le llama elemento.

Hay dos formas de inicializar una matriz:

  • Como una lista de valores separados por comas.
  • Especificando el valor inicial, seguido de punto y coma y, después, la longitud de la matriz.
let matriz_1 = ["Alfa", "Beta", "Gamma", "Delta", "Epsilon"];
let matriz_2 = [7; 10]; // Inicializa una matriz de 10 elementos en el que cada uno de ellos tiene el valor 7

Una matriz tiene un tamaño fijo y no puede aumentar o disminuir a lo largo de la ejecución del programa.

Para acceder a los valores de una matriz se usa un índice que indica su posición dentro de la tupla. El primer elemento de la matriz tiene el índice 0, el segundo el 1 y así sucesivamente.

println!("{:?}", matriz_1[0]); // "Alfa"
println!("{:?}", matriz_1[1]); // "Beta"
println!("{:?}", matriz_1[2]); // "Gamma"
println!("{:?}", matriz_1[3]); // "Delta"
println!("{:?}", matriz_1[4]); // "Epsilon"

Tratar de acceder a un valor mediante un índice igual o mayor que el tamaño de la matriz dará un error en tiempo de compilación.

println!("{:?}", matriz_1[8]);

// Al compilar este código obtenemos el siguiente error:

error: this operation will panic at runtime
  --> src/main.rs:10:18
   |
   | println!("{:?}", matriz_1[8]);
   |                  ^^^^^^^^^^^ index out of bounds: the length is 5 but the index is 8
   |

Es posible modificar el valor de un elemento (siempre que la matriz sea declarada como mutable, mut).

matriz_1[2] = "Phi";
println!("{:?}", matriz_1[2]); // Mostrará "Phi"

Pero ha de ser por un valor del mismo tipo. La inicialización de una matriz marca el tipo de cada elemento a lo largo de todo el código. Y éste es evaluado en tiempo de compilación.

matriz_1[2] = 10i32;
println!("{:?}", matriz_1[2]);

error[E0308]: mismatched types
  --> src/main.rs:10:15
   |
   | matriz_1[2] = 10i32;
   | -----------   ^^^^^ expected `&str`, found `i32`
   | |
   | expected due to the type of this binding

Colecciones

Vector

Un vector, Vec<T>, almacena valores de un mismo tipo.

Hay dos formas de declarar un vector:

  • Mediante el método new() de la estructura Vec.

    let v = Vec::new();  
    
  • Mediante la macro vec!.

    let v = vec![val1, val2, val3];
    

A diferencia de una matriz, un vector no tiene un tamaño fijo, por lo tanto pueden agregarse o eliminarse elementos a lo largo de la ejecución del programa.

Se usa el método push para añadir elementos.

let mut primos = Vec::new();
primos.push(2);
primos.push(3);
primos.push(5);

// El contenido de primos es:
// [2, 3, 5]

Solo se pueden añadir elementos del tipo adecuado. El tipo lo marca el primer elemento que se añade.

primos.push(3.4);

// Al compilar este código obtenemos el siguiente error:

  |
  |     primos.push(3.4);
  |                 ^^^ expected integer, found floating-point number

// El mismo error nos encontraremos a declarar un vector con la macro vec!

let mut primos = vec![2,3,5.5,7];

  |
  |     let mut primos = vec![2,3,5.5,7];
  |                               ^^^ expected integer, found floating-point number

Para acceder a los elementos de un vector se usa el índice que lo posiciona dentro del vector. El primer elemento tiene el índice 0, el segundo el 1 y así sucesivamente.

println!("{:?}", primos[0]); // 2
println!("{:?}", primos[1]); // 3
println!("{:?}", primos[2]); // 5
println!("{:?}", primos[4]); // 7

Tratar de acceder a un elemento mediante un índice igual o mayor que el tamaño del vector resultará en un error en tiempo de ejecución.

println!("{:?}", primos[9]);

// Al ejecutar este código obtendremos el siguiente error:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 9'

Para eliminar elementos se usa el método remove especificando el índice del elemento a eliminar.

let mut primos = vec![2,3,5];

primos.remove(1);

// El contenido de primos es:
// [2, 5]

Tratar de eliminar un elemento mediante un índice igual o mayor que el tamaño del vector resultará en un error en tiempo de ejecución.

let mut primos = vec![2,3,5];

primos.remove(4);

// Al ejecutar este código obtendremos el siguiente error:

thread 'main' panicked at 'removal index (is 4) should be < len (is 3)',

Es posible modificar el valor de un elemento (siempre que el vector sea declarado como mutable, mut).

primos[2] = 9;

// El contenido de primos es:
// [2, 3, 9]

Mapa Hash

El tipo HashMap<K, V> almacena datos asignando cada clave K con su valor V.

Un mapa hash se inicializa de la siguiente manera:

use std::collections::HashMap;
let mut films: HashMap<String, String> = HashMap::new(); 
// En este caso tanto la clave como el valor son del tipo String, pero pueden ser de cualquier otro tipo.

Un mapa hash no tiene un tamaño fijo, éste puede aumentar o dismuir a lo largo de la ejecución del programa.

Se usa el método insert para añadir elementos.

films.insert(String::from("El bueno, el feo y el malo"), String::from("Sergio Leone"));
films.insert(String::from("La noche del cazador"), String::from("Charles Laughton"));
films.insert(String::from("Los bingueros"), String::from("Mariano Ozores"));

// El contenido de films es:
// {"El bueno, el feo y el malo": "Sergio Leone", "Matar a un ruiseñor": "Robert Mulligan", "Los bingueros": "Mariano Ozores"} 

Solo se pueden añadir claves y valores del tipo estipulado en la declaración del mapa hash.

films.insert(String::from("Siete"), 7);

// Al compilar este código obtenemos el siguiente error:

   |
   | films.insert(String::from("Siete"), 7);
   |                                     ^- help: try using a conversion method: `.to_string()`
   |                                     |
   |                                     expected struct `String`, found integer

Podemos eliminar elementos usando el método remove.

films.remove("Los bingueros");

// El contenido de films es:
// {"El bueno, el feo y el malo": "Sergio Leone", "Matar a un ruiseñor": "Robert Mulligan"}

Tratar de eliminar un elemento que no existe nos devolverá un None.

Se usa el método get para obtener el valor asignado a una clave.

println!("{}", films.get("Los bingueros"));

// Nos devolverá:
// Some("Mariano Ozores")

Trater de obtener un valor mediante una clave que no existe nos devolverá un None.

Enlaces de referencia