Un slice es una referencia a una secuencia de elementos contiguos en memoria. A diferencia de una referencia a un tipo único (como &i32), un slice es lo que se conoce como un “fat pointer” (puntero gordo). Esto significa que, internamente, un slice &[T] no es solo una dirección de memoria, sino una estructura compuesta por dos “words”: un puntero al inicio de la secuencia y una longitud que indica cuántos elementos contiene. Esta estructura permite que el programa acceda a una parte de un contenedor sin necesidad de conocer la capacidad total o el contenedor original, garantizando seguridad mediante la verificación de límites.

En el diseño de APIs, es una práctica fundamental preferir el uso de slices (&[T]) en lugar de referencias a contenedores específicos como &Vec<T> o &[T; N]. Esto se debe a la coerción de desreferencia (Deref coercion). Gracias a que Vec<T> implementa el trait Deref<Target = [T]>, el compilador de Rust puede convertir automáticamente una referencia a un vector (&Vec<T>) en una referencia a un slice (&[T]). Lo mismo ocurre con &String convirtiéndose en &str. Al aceptar un slice, su función se vuelve extremadamente flexible: puede recibir un Vec, un array o incluso un subconjunto de otro slice, permitiendo una reutilización de código superior. Si se cometiera el error de diseñar una función que solo acepte &Vec<T>, se estaría limitando innecesariamente al usuario, obligándolo a empaquetar arrays en vectores para poder llamar a la función.

Los slices ofrecen un conjunto de métodos altamente optimizados para la manipulación de secuencias, tales como sort para ordenar, windows(n) para obtener iteradores sobre vistas deslizantes, o chunks(n) para dividir la secuencia en trozos de tamaño fijo.

fn main() {
    let vector = vec![40, 10, 30, 20, 50];
    let array = [5, 4, 3, 2, 1];
    let mut datos_mutables = [10, 50, 20, 40, 30];

    // Coerción de Vec<T> a &[i32]
    imprimir_detalles(&vector);

    // Coerción de Array a &[i32] y uso de sub-slice
    imprimir_detalles(&array[1..4]);

    // Uso de métodos de ventana (sliding windows)
    mostrar_ventanas(&vector);

    // Manipulación directa sobre el slice mutable
    procesar_secuencia(&mut datos_mutables);
}

fn imprimir_detalles(slice: &[i32]) {
    println!("Longitud del slice: {}, Primer elemento: {}", slice.len(), slice[0]);
}

fn mostrar_ventanas(slice: &[i32]) {
    // windows(2) crea iteradores sobre elementos contiguos de tamaño 2
    for ventana in slice.windows(2) {
        println!("Ventana actual: {:?}", ventana);
    }
}

fn procesar_secuencia(slice: &mut [i32]) {
    // El slice permite ordenar los elementos directamente en su ubicación de memoria
    slice.sort();
    println!("Slice ordenado: {:?}", slice);

    if slice.contains(&30) {
        println!("El valor 30 fue encontrado.");
    }
}

Explicación del Código

  • vector: Una instancia de Vec<i32>. Al pasarse a imprimir_detalles, ocurre la Deref coercion, transformándose en un &[i32].
  • array: Un array de tipo [i32; 5]. Al usar &array[1..4], se crea un slice que apunta desde el índice 1 hasta el 3.
  • imprimir_detalles(slice: &[i32]): Esta función recibe un “fat pointer”. Dentro, slice.len() accede a la segunda parte del puntero (la longitud) y slice[0] utiliza el puntero base para acceder al primer elemento.
  • mostrar_ventanas(slice: &[i32]): Utiliza el método windows(2) sobre el slice para crear iteradores de pares contiguos.
  • datos_mutables: Un array que se pasa como una referencia mutable &mut [i32].
  • procesar_secuencia(slice: &mut [i32]):
    • slice.sort(): Aplica un algoritmo de ordenamiento directamente sobre la memoria referenciada por el puntero.
    • slice.contains(&30): Utiliza un método de búsqueda para verificar la presencia de un valor dentro de los límites definidos por el slice.

34