Un slice es una referencia a una parte continua de una colección o cadena.
Ya sabemos que el Ownership controla quién limpia la memoria y el Borrowing nos permite prestar datos. A veces no queremos prestar todo el objeto, sino solo una parte.
Imagina que tienes un libro enorme (String).
- Si pasas el libro por valor (Move), lo regalas.
- Si pasas una referencia al libro (
&String), dejas que lo lean entero. - ¿Y si solo quieres que lean el capítulo 3?
Una opción sería copiar el texto del capítulo 3 en una variable nueva, pero eso duplica memoria. En Rust, podemos usar slices (rebanadas) para crear una vista prestada.
Un slice es una referencia a una secuencia contigua de elementos en lugar de a la colección entera. Es como una “ventana” sobre los datos originales: no posee los elementos y guarda un puntero junto con una longitud.
String slices (&str)
El uso más común de los slices es con cadenas de texto. Seguramente te has fijado en que a veces usamos String y otras veces &str. Vamos a entender la diferencia por fin.
Supongamos que tenemos un String en el Heap:
fn main() {
let s = String::from("Hola Mundo");
let hola = &s[0..4]; // Slice de los índices 0, 1, 2, 3
let mundo = &s[5..10]; // Slice de los índices 5 al 9
println!("Primera parte: {}", hola);
}La sintaxis [start..end] crea un rango.
holaes un slice&str.- Internamente,
holacontiene dos datos en el Stack:
- Un puntero al byte donde empieza la ‘H’.
- Una longitud (4 bytes).
No hemos copiado “Hola”. El slice sigue apuntando a los bytes que pertenecen a s, por lo que el compilador impide modificar de forma incompatible la cadena original mientras usemos esa referencia.
Atajos de sintaxis Rust es inteligente con los rangos:
[0..2]es igual a[..2](desde el principio).[3..len]es igual a[3..](hasta el final).[..]es un slice de todo el contenido.
Literales de str
Aquí viene una revelación importante. Cuando escribes esto:
let s = "Hola mundo";¿De qué tipo es s? No es un String. Es un &str.
Es un &'static str: una referencia a bytes UTF-8 incluidos en los datos estáticos del programa y disponibles durante toda su ejecución. Por eso un literal de cadena es inmutable y no necesita una reserva dinámica.
String vs &str
Esta es una de las dudas más frecuentes.
String: Es propietario de un búfer en el Heap. Puede crecer, encogerse y modificarse, y reservar memoria tiene un coste.&str: Es una vista (Slice). Es ligero (solo puntero + longitud). No es dueño de los datos. Normalmente es lo que quieres pasar como argumento a tus funciones.
Criterio práctico:
Si escribes una función que solo necesita leer un texto, usa &str como parámetro, no &String.
// ❌ Restrictivo: solo acepta Strings del Heap
fn saludar(nombre: &String) { ... }
// ✅ Flexible: acepta String, partes de String y literales
fn saludar(nombre: &str) { ... }Otros slices (&[T])
Los slices no son exclusivos de las cadenas de texto. Funcionan con secuencias contiguas, como los arrays y los vectores.
Imagina que tienes un array de 5 números y quieres pasarle a una función solo los 3 del medio.
fn main() {
let a = [10, 20, 30, 40, 50];
// Tipo: &[i32]
let slice = &a[1..4];
assert_eq!(slice, &[20, 30, 40]);
}El tipo se denota como &[T], donde T es el tipo de dato (en este caso &[i32]).
Esto es tremendamente potente para escribir APIs eficientes. Si tu función acepta &[i32], puedes llamarla pasando:
- Una referencia a un array entero (
&[1, 2]). - Una referencia a un vector (
&vec). - Un slice parcial de cualquiera de los dos.
Slices y el Borrow Checker
Como los slices son referencias, están sujetos a las reglas de Borrowing que vimos en el artículo anterior. Y aquí es donde Rust te salva de errores graves de memoria.
Imagina este escenario (común en C++):
- Tienes un Vector.
- Creas un puntero al primer elemento.
- Destruyes el vector o fuerzas una reasignación de su búfer.
- Intentas leer el puntero.
- Resultado en C++: Use After Free (Crash o vulnerabilidad).
Veamos qué pasa en Rust:
fn main() {
let mut s = String::from("hola mundo");
let palabra = &s[..4]; // Préstamo inmutable (slice)
s.clear(); // ❌ ERROR DE COMPILACIÓN
println!("La palabra es: {}", palabra);
}El error será: “cannot borrow s as mutable because it is also borrowed as immutable”.
palabrapide prestados(inmutable).s.clear()intenta pedir prestados(mutable) para borrarlo.- Rust dice: “¡Alto!”. Mientras
palabraesté viva (se usa en elprintln), no puedes modificars.palabradejaría de apuntar a datos válidos.
El sistema de préstamos garantiza que un slice de código seguro no quede apuntando a memoria liberada o invalidada.
Cuidado con UTF-8
Una advertencia final específica de los Strings. Rust usa UTF-8 por defecto. Algunos caracteres (como los emojis o letras con tilde) ocupan más de 1 byte.
Si intentas hacer un slice que corta un carácter por la mitad, Rust entrará en pánico (panic!) en tiempo de ejecución.
let s = "Mañana"; // La 'ñ' ocupa 2 bytes
// let slice = &s[0..3]; // 💀 Peligro: Podrías estar partiendo la ñPor suerte, es raro tener que manipular índices de strings a mano en Rust. Solemos usar iteradores o métodos que manejan UTF-8 correctamente.