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();
}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];
}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");
}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);
}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"),
}
}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);
}
}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);
}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í
}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
}Esto encaja con todo lo que vimos en ownership. El vector no es una excepción rara: es otro dueño más.