Slices en Rust
Un slice (porción) te permite tomar prestada una parte de un array o de un vector, una especie de “ventana” a una parte de los datos, sin copiarlos ni quedarte con su ownership. Si ya usaste el borrowing (préstamo) con un solo valor, un slice es la misma idea aplicada a un rango de elementos.
Tomar un slice de un array
Sección titulada «Tomar un slice de un array»fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // los elementos de los índices 1, 2 y 3 println!("{:?}", middle);}Salida:
[20, 30, 40]1..4 es el mismo tipo de rango que viste en los bucles for: empieza en el índice 1 y se detiene antes del índice 4, así que obtienes los índices 1, 2 y 3.
Atajos para los rangos
Sección titulada «Atajos para los rangos»Algunos atajos que vas a ver a menudo:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // desde el principio hasta el índice 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // desde el índice 2 hasta el final -> [30, 40, 50] println!("{:?}", &numbers[..]); // todo completo -> [10, 20, 30, 40, 50]}Slices de cadenas de texto
Sección titulada «Slices de cadenas de texto»En realidad ya has estado usando slices: ¡&str es un slice de un String (o de otra cadena)! Por eso los dos tipos se llevan tan bien:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}Aquí dejamos el texto en inglés a propósito. Los rangos de un &str cuentan bytes, no letras, y caracteres como la “á” o la “ñ” ocupan 2 bytes en UTF-8. Con un texto en español, un rango como 0..3 podría caer en medio de una letra y el programa haría panic. Lo verás con más detalle en la página de Strings.
¿Por qué slices en lugar de copias?
Sección titulada «¿Por qué slices en lugar de copias?»Podrías pensar en resolver esto copiando la parte que quieres en un Vec o un String nuevo. Los slices evitan eso por completo: solo apuntan a una parte de los datos que ya existen, igual que una referencia (&) apunta a un único valor. Eso significa que no hay memoria extra, no hay copias, y siguen valiendo las reglas de borrowing que ya conoces de la página de Borrowing.
Una función que recibe un slice
Sección titulada «Una función que recibe un slice»Los slices son especialmente útiles como parámetros de función, porque aceptan tanto arrays como vectores:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] acepta un slice tanto de un array como de un Vec: una sola función, y las dos llamadas funcionan.
Modificar datos a través de un slice mutable
Sección titulada «Modificar datos a través de un slice mutable»Hasta ahora, nuestros slices solo han leído datos. Un slice mutable, &mut [T], permite que una función modifique los elementos que recibió, pero solo esos:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // solo los índices 1 y 2 println!("{:?}", scores);
double_all(&mut scores); // el vector completo println!("{:?}", scores);}Salida:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]La primera llamada pasa &mut scores[1..3], así que double_all solo ve los índices 1 y 2. Todo lo que queda fuera de ese rango está a salvo. Dentro de la función, el slice vuelve a empezar en el índice 0: para double_all, es simplemente una lista de dos números.
Esto es seguro gracias a una regla que conocerás a fondo en la página de Borrowing: mientras la función tiene un slice &mut, nada más puede usar esos elementos, así que dos partes de tu código nunca pueden modificarlos a la vez. Además, el vector tiene que declararse con mut, o Rust no te dejará tomarlo prestado de forma mutable.
Práctica
Sección titulada «Práctica»1. Partir por la mitad
Sección titulada «1. Partir por la mitad»Crea el vector vec![3, 6, 9, 12, 15, 18]. Imprime su primera mitad y su segunda mitad como dos slices separados. Pista: calcula let mid = numbers.len() / 2; y usa ..mid y mid...
Ver solución
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Primera mitad: {:?}", first_half); println!("Segunda mitad: {:?}", second_half);}Salida:
Primera mitad: [3, 6, 9]Segunda mitad: [12, 15, 18]Como mid se calcula a partir de len(), esto sigue funcionando si añades o quitas números. Con una longitud impar, el elemento que sobra acaba en la segunda mitad.
2. El mayor de cualquier lista
Sección titulada «2. El mayor de cualquier lista»Escribe una función largest(numbers: &[i32]) -> i32 que devuelva el número más grande. Llámala con un array, con un vector y con un slice que tenga solo el primer elemento del vector. Pista: empieza con numbers[0] y recorre el resto, como harías con un array.
Ver solución
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Salida:
93510Al recorrer un slice, recibes una referencia a cada elemento (&i32), no el número en sí. *n significa “el valor al que apunta n”, así que *n > biggest compara dos números normales. Esta versión hace panic con un slice vacío, porque numbers[0] no existe. Más adelante aprenderás a devolver un Option para ese caso.
Resumen
Sección titulada «Resumen»- Un slice (
&[T]) toma prestada una parte de un array o vector sin copiarla. 1..4selecciona los índices del 1 al 3; omite uno de los lados de..para ir desde el principio o hasta el final.&stres en sí mismo un slice de texto; por eso las cadenas y los slices se comportan de forma tan parecida.- Usa
&[T]como tipo de parámetro cuando quieras que una función acepte tanto arrays como vectores. - Un slice mutable (
&mut [T]) permite que una función modifique solo los elementos que recibió.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué te permite hacer un slice con una parte de un array o vector?
2. ¿Qué índices incluye &numbers[1..4]?
3. ¿Por qué &[i32] es una buena elección como tipo de parámetro de una función?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- El tipo slice — El libro de Rust (en inglés): llega a los slices paso a paso con un ejemplo de “la primera palabra de una cadena”.
- Arrays y slices — Rust by Example (en inglés): ejemplos ejecutables de cómo tomar prestados arrays como slices.
slice— documentación de la biblioteca estándar (en inglés): los muchos métodos que tienen los slices, comofirst,split_at,containsysort.- Tipos slice — The Rust Reference (en inglés): la definición precisa, por si quieres saber exactamente qué significan
[T]y&[T].