rust-vectores-vec

Vectores en Rust con Vec<T>: crear, acceder e iterar

  • 3 min

Un vector en Rust es una colección dinámica que guarda varios valores del mismo tipo en memoria contigua.

Si un array era una caja con huecos fijos, un Vec<T> es una caja que puede crecer cuando necesitamos meter más cosas. Por eso es una de las colecciones que más vais a usar.

Crear un vector

Podemos crear un vector vacío con Vec::new():

fn main() {
    let mut numeros: Vec<i32> = Vec::new();
}
Copied!

Aquí hemos puesto el tipo explícito porque el vector está vacío y Rust no puede adivinar qué vamos a guardar dentro.

También podemos usar la macro vec!, que es lo más cómodo cuando ya tenemos valores:

fn main() {
    let numeros = vec![1, 2, 3, 4, 5];
}
Copied!

En este caso Rust deduce que numeros es un Vec<i32>.

Añadir elementos

Para añadir valores usamos push.

fn main() {
    let mut tareas = Vec::new();

    tareas.push("Comprar pan");
    tareas.push("Aprender Rust");
    tareas.push("No pelearse con el borrow checker");
}
Copied!

Fijaos en el mut. Un vector puede crecer, pero solo si la variable que lo contiene es mutable.

Acceder a elementos

Tenemos dos formas principales de acceder a un elemento.

La primera es con índices:

fn main() {
    let numeros = vec![10, 20, 30];
    let segundo = numeros[1];

    println!("{}", segundo);
}
Copied!

Esto es directo, pero si el índice no existe, el programa hará panic!.

La segunda forma es con get, que devuelve un Option:

fn main() {
    let numeros = vec![10, 20, 30];

    match numeros.get(10) {
        Some(valor) => println!("Valor: {}", valor),
        None => println!("Ese índice no existe"),
    }
}
Copied!

get es más seguro cuando el índice puede venir de fuera, como una entrada de usuario o una API.

Si el índice lo controláis vosotros y sabéis que existe, usar [] está bien. Si el índice viene de fuera, get suele ser mejor opción.

Recorrer un vector

Podemos recorrerlo sin consumirlo usando referencias:

fn main() {
    let numeros = vec![1, 2, 3];

    for numero in &numeros {
        println!("{}", numero);
    }
}
Copied!

Si queremos modificar los valores, usamos referencias mutables:

fn main() {
    let mut numeros = vec![1, 2, 3];

    for numero in &mut numeros {
        *numero *= 2;
    }

    println!("{:?}", numeros);
}
Copied!

Ese *numero es la desreferenciación. numero no es el entero directamente, es una referencia mutable al entero.

Consumir un vector

Si iteramos sin &, movemos los valores fuera del vector:

fn main() {
    let nombres = vec![String::from("Ana"), String::from("Luis")];

    for nombre in nombres {
        println!("{}", nombre);
    }

    // nombres ya no se puede usar aquí
}
Copied!

Esto es correcto si ya no necesitamos el vector después. Si queremos conservarlo, iteramos con &nombres.

Vectores y ownership

Un Vec<T> es dueño de sus elementos. Cuando el vector sale de ámbito, Rust libera todos los valores que contiene.

fn main() {
    let textos = vec![
        String::from("uno"),
        String::from("dos"),
    ];

    // Al salir de main, se liberan el Vec y sus Strings
}
Copied!

Esto encaja con todo lo que vimos en ownership. El vector no es una excepción rara: es otro dueño más.