Lección 13 de 26 Colecciones y errores
Vectores: Vec<T>
- Un array tiene tamaño fijo. Un vector Vec<T> crece y encoge: ideal cuando no sabes cuántos datos tendrás.
- push añade un elemento al final. Por eso pila es mut: el vector va a cambiar.
- pop quita el último y devuelve un Option: Some(3), o None si el vector está vacío.
- Los índices empiezan en 0. pila[5] hace panic porque no existe; pila.get(5) devuelve None sin fallar.
Los arrays tienen un tamaño fijo que se decide al compilar: un [i32; 3] siempre tendrá tres elementos. Pero casi nunca sabes de antemano cuántos datos vas a manejar: los productos de un carrito, los mensajes de un chat, las líneas de un fichero…
Para eso está Vec<T>, el vector: una lista de elementos del mismo tipo que puede crecer y encoger mientras el programa se ejecuta. Sus elementos se guardan en el heap, uno detrás de otro. Es, con diferencia, la colección más usada de Rust.
Crear un vector
Hay dos formas: Vec::new() crea un vector vacío y la macro vec! lo crea con valores iniciales.
fn main() {
let vacio: Vec<i32> = Vec::new();
let letras = vec!['a', 'b', 'c'];
println!("{vacio:?} {letras:?}");
println!("Letras: {}", letras.len());
println!("¿Vacío? {}", vacio.is_empty());
}Con Vec::new() indicamos el tipo (Vec<i32>) porque el vector está vacío y Rust no tiene de dónde deducirlo. Con vec! no hace falta: lo saca de los valores. len() devuelve cuántos elementos hay y is_empty() si no hay ninguno.
Añadir y quitar: push y pop
push añade un elemento al final y pop quita el último. Como el vector tiene que cambiar, debe ser mut:
fn main() {
let mut pila = Vec::new();
pila.push(1);
pila.push(2);
pila.push(3);
println!("{:?}", pila.pop()); // Some(3)
println!("{pila:?}"); // [1, 2]
let mut vacia: Vec<i32> = Vec::new();
println!("{:?}", vacia.pop()); // None
}Fíjate en que aquí no hace falta indicar el tipo de pila: Rust lo deduce del primer push. Y pop devuelve un Option: un vector vacío no tiene último elemento que quitar, así que devuelve None en lugar de fallar.
Leer elementos: índice o get
Con v[i] accedes a la posición i (empezando en 0), como en un array. Pero si la posición no existe, el programa hace panic:
fn main() {
let v = vec![10, 20, 30];
println!("{}", v[1]);
println!("{}", v[10]);
}Si la posición puede no existir, usa get. Devuelve un Option con una referencia al elemento:
fn main() {
let v = vec![10, 20, 30];
match v.get(10) {
Some(x) => println!("Elemento: {x}"),
None => println!("No hay nada en esa posición"),
}
}Regla práctica: Usa
v[i]cuando sea un error de programación que la posición no exista ygetcuando sea algo normal, por ejemplo si el índice lo escribe el usuario.
Recorrer un vector
Con for x in &v recorres el vector prestado: x es una referencia a cada elemento y el vector sigue siendo tuyo después del bucle. Si quieres modificar los elementos, usa &mut v y el operador * para cambiar el valor al que apunta cada referencia:
fn main() {
let mut precios = vec![10, 20, 30];
for p in &precios {
println!("Precio: {p}");
}
for p in &mut precios {
*p += 5;
}
println!("{precios:?}");
}Si escribes for x in v, sin &, el bucle se queda con el vector (lo mueve) y no podrás usarlo después.
Vectores y préstamos
Las reglas de los préstamos también se aplican aquí. Este código parece inofensivo, pero no compila:
fn main() {
let mut v = vec![1, 2, 3];
let primero = &v[0];
v.push(4);
println!("{primero}");
}primero es un préstamo de v y, mientras siga vivo, no puedes modificar v. No es un capricho: si el vector no tiene sitio para el nuevo elemento, push reserva un bloque de memoria más grande, copia ahí los elementos y libera el antiguo. primero seguiría apuntando a la memoria liberada.
En otros lenguajes este error aparece de vez en cuando en ejecución y es difícil de encontrar. En Rust, el compilador lo detecta antes de que exista.
Otros métodos útiles
fn main() {
let mut v = vec![5, 3, 8];
v.insert(0, 9); // [9, 5, 3, 8]
let quitado = v.remove(2); // quita el 3: [9, 5, 8]
v.sort(); // [5, 8, 9]
println!("{v:?}, he quitado el {quitado}");
println!("¿Tiene un 8? {}", v.contains(&8));
}
| Método | Qué hace |
|---|---|
push(x) | Añade x al final |
pop() | Quita el último y lo devuelve en un Option |
insert(i, x) | Mete x en la posición i y desplaza el resto |
remove(i) | Quita el elemento de la posición i y lo devuelve |
get(i) | Devuelve Some(&x) o None |
contains(&x) | true si el vector contiene x |
sort() | Ordena el vector de menor a mayor |
len() / is_empty() | Número de elementos / si está vacío |
contains recibe una referencia (&8) porque solo necesita mirar el valor, no quedárselo. Y ten en cuenta que insert y remove tienen que desplazar los elementos que van detrás: en vectores enormes son más lentos que push y pop.
Funciones que reciben vectores
Si una función solo necesita leer el vector, recibe un slice &[T] en lugar de &Vec<T>. Así acepta tanto &Vec<T> como arrays:
fn sumar(numeros: &[i32]) -> i32 {
let mut total = 0;
for n in numeros {
total += n;
}
total
}
fn main() {
let v = vec![1, 2, 3];
println!("{}", sumar(&v));
println!("{}", sumar(&[10, 20]));
}Mezclar tipos con un enum
Un vector solo guarda elementos de un tipo. Si necesitas mezclar, por ejemplo, las celdas de una hoja de cálculo, crea un enum con una variante por cada tipo: todas son del mismo tipo para el vector.
enum Celda {
Entero(i32),
Decimal(f64),
Texto(String),
}
fn main() {
let fila = vec![
Celda::Entero(3),
Celda::Texto(String::from("azul")),
Celda::Decimal(10.5),
];
for celda in &fila {
match celda {
Celda::Entero(n) => println!("Entero: {n}"),
Celda::Decimal(d) => println!("Decimal: {d}"),
Celda::Texto(t) => println!("Texto: {t}"),
}
}
}Y como match es exhaustivo, el compilador comprueba que tratas todos los tipos posibles.
Hora de practicar
Ejercicios
0/4 resueltos
Reto 1 de 4 Resuelto
compra con Vec::new(), añade "pan", "leche" y "huevos" con push y después quita el último con pop. Imprime lo que devuelve pop y cómo queda el vector, exactamente como indican los comentarios.Quitado: Some("huevos")
Quedan 2: ["pan", "leche"] Al ejecutar, unos tests ocultos comprueban tus funciones.
Pulsa Ejecutar para compilar y ver aquí la salida de tu programa.