Un slice es una referencia a una parte contigua de una colección de datos, como un array o un Vec<T>. A diferencia de una colección, un slice no posee los datos; es simplemente una “vista” que permite inspeccionar o manipular un subconjunto de la memoria original sin necesidad de copiar los elementos.

Técnicamente, un slice es un “puntero pesado” (fat pointer). Mientras que un puntero estándar solo contiene la dirección de memoria de un elemento, un slice en Rust es una estructura compuesta por dos valores: un puntero al primer elemento de la secuencia y la longitud (length) de la porción seleccionada. Esto permite que Rust verifique los límites de la memoria en tiempo de ejecución, garantizando la seguridad.

Se utilizan slices principalmente para mejorar la eficiencia (evitando copias innecesarias) y para la abstracción. Por ejemplo, en lugar de escribir una función que acepte solo arrays de tamaño fijo, se utiliza &[T] para que la función pueda aceptar tanto arrays como Vec<T>. Un error común al trabajar con slices es intentar mantener una referencia a un slice después de que la colección original (su dueña) haya salido de ámbito (scope), lo que resultaría en un error de lifetime o puntero colgante.

fn main() {
    // 1. Slices sobre un array
    let arraya = [10, 20, 30, 40, 50];
    let slice_parcial = &arraya[1..4];   // Rango exclusivo: elementos en índices 1, 2, 3
    let slice_inicio = &arraya[..2];    // Desde el inicio hasta índice 1
    let slice_final = &arraya[3..];     // Desde índice 3 hasta el final
    let slice_todo = &arraya[..];       // Referencia a toda la secuencia

    // 2. Slices sobre un Vec (funcionan igual que los arrays)
    let vec_dinamico = vec![1, 2, 3, 4, 5];
    let slice_vec = &vec_dinamico[1..4];

    // 3. Slices de strings (&str)
    // Las strings en Rust son secuencias UTF-8; &str es un slice de ellas
    let cadena = String::from("Hola Rust");
    let slice_texto: &str = &cadena[0..4]; // "Hola"

    // 4. Métodos y seguridad
    println!("Slice parcial: {:?}", slice_parcial);
    println!("¿El slice_vec tiene elementos?: {}", slice_vec.is_empty());
    
    // Uso de .get() para evitar pánico (retorna Option)
    match slice_parcial.get(10) {
        Some(val) => println!("Valor encontrado: {}", val),
        None => println!("Índice fuera de rango de forma segura"),
    }

    // 5. Flexibilidad: funciones que aceptan slices
    procesar_datos(slice_parcial);
    procesar_datos(slice_vec);

    // 6. Mutabilidad (requiere declarar la colección como mut)
    let mut arraya_mut = [1, 2, 3, 4, 5];
    let slice_mut = &mut arraya_mut[0..3];
    slice_mut.reverse(); // Invierte el subconjunto seleccionado
    println!("Array mutado: {:?}", arraya_mut);
}

// Esta función acepta cualquier secuencia de i32 (array o Vec) mediante un slice
fn procesar_datos(datos: &[i32]) {
    let suma: i32 = datos.iter().sum();
    println!("La suma de la secuencia es: {}", suma);
}

Explicación del Código

  • arraya: Un array estático de tipo [i32; 5]. Se utiliza para demostrar cómo crear diferentes tipos de vistas mediante la sintaxis de rangos.
  • slice_parcial: Un slice de tipo &[i32] que apunta a los elementos 20, 30, 40. El rango 1..4 indica que el límite superior es exclusivo.
  • slice_inicio, slice_final y slice_todo: Demuestran la versatilidad de la sintaxis de rangos (..) para omitir el inicio o el final de la secuencia.
  • vec_dinamico y slice_vec: Muestran que un Vec<T> puede ser “rebanado” de la misma forma que un array, creando un &[T].
  • cadena y slice_texto: cadena es un String (poseído), mientras que slice_texto es un &str (un slice de caracteres UTF-8).
  • slice_parcial.get(10): El método get devuelve un Option<&T>. A diferencia del acceso directo con [] (que causaría un panic si el índice no existe), get permite manejar la ausencia de datos de forma segura mediante un match.
  • procesar_datos: Esta función es clave para la modularidad. Al aceptar datos: &[i32] en lugar de Vec<i32>, permite que el programa sea más genérico y eficiente, aceptando tanto el slice_parcial (del array) como el slice_vec (del vector) sin necesidad de realizar copias de memoria o usar genéricos complejos.
  • slice_mut: Un slice mutable que permite modificar directamente el contenido de arraya_mut mediante métodos como reverse(), demostrando que los slices pueden ser tanto inmutables como mutables.

17