Por qué &String, &Vec y &Box son de mal gusto en Rust?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: La trampa en la que caen todos los principiantes en Rust

\n\n

Estás aprendiendo Rust. Ya has entendido el concepto de propiedad y préstamo. Escribes tu primera función seria y piensas: «¡Ajá, para no mover la cadena, pasaré una referencia!». Y escribes:

\n\n
fn print_string(s: &String) {\n    println!("{}", s);\n}
\n\n

El compilador está feliz. El código funciona. Tú estás feliz. Pero aquí está el problema: un desarrollador experimentado de Rust, al ver tu código, hará una mueca. ¿Por qué? ¡Si todo funciona!

\n\n

Esta pregunta es una de las más populares en StackOverflow. Miles de desarrolladores tropiezan con este detalle aparentemente insignificante. Hoy analizaremos por qué &String, &Vec y &Box en la firma de una función son una bandera roja, y cómo escribir correctamente.

\n\n

Problema #1: Restricción implícita de tipos (Type Coercion)

\n\n

Rust tiene un potente sistema de coerción automática de tipos, pero solo en una dirección. Esto se llama Deref Coercion (coerción por desreferencia).

\n\n

La idea es simple: si tienes una referencia a String, Rust automáticamente puede convertirla en una referencia a &str. Lo mismo ocurre con &Vec<T> → &[T] y &Box<T> → &T.

\n\n

Observa la diferencia:

\n\n
// Malo: solo acepta &String\nfn bad(s: &String) {\n    println!("{}", s);\n}\n\n// Bueno: acepta &str\nfn good(s: &str) {\n    println!("{}", s);\n}\n\nfn main() {\n    let owned = String::from("¡Hola, mundo!");\n    let slice = "¡Hola, slice!";\n    \n    bad(&owned); // Funciona\n    // bad(slice); // ¡ERROR! slice es &str, no &String\n    \n    good(&owned); // Funciona (coerción automática)\n    good(slice);   // Funciona (ya es &str)\n}
\n\n

¿Ves? La función good acepta cualquier cadena: tanto el propietario String, como el literal de cadena &str, e incluso una parte de la cadena. La función bad solo acepta String. Esto es una restricción innecesaria.

\n\n

Ejemplo práctico: búsqueda de subcadena

\n\n
// Búsqueda limitada\nfn find_in_string(haystack: &String, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\n// Búsqueda universal\nfn find_in_str(haystack: &str, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\nfn main() {\n    let my_string = String::from("Rust es increíble");\n    let my_str = "Rust es increíble";\n    \n    // Solo funciona con String\n    find_in_string(&my_string, "Rust");\n    // find_in_str(my_str, "Rust"); // ¡Error de compilación!\n    \n    // Funciona con ambos\n    find_in_str(&my_string, "Rust");\n    find_in_str(my_str, "Rust");\n}
\n\n

Problema #2: Rendimiento y direccionamiento indirecto innecesario

\n\n

Miremos «bajo el capó». ¿Qué es String en memoria? Es una estructura de tres campos:

\n
    \n
  • Puntero a los datos en el heap
  • \n
  • Longitud de la cadena
  • \n
  • Capacidad (capacity)
  • \n
\n\n

Cuando pasas &String, pasas una referencia a esta estructura. Para llegar a los datos, el compilador necesita:

\n
    \n
  1. Desreferenciar &String → obtener la estructura String
  2. \n
  3. De la estructura, tomar el puntero a los datos en el heap
  4. \n
  5. Desreferenciar este puntero → obtener los bytes de la cadena
  6. \n
\n\n

Cuando pasas &str, pasas un fat pointer (puntero gordo) que consta de solo dos campos:

\n
    \n
  • Puntero a los datos
  • \n
  • Longitud
  • \n
\n\n

El compilador necesita hacer solo un paso — desreferenciar el puntero. Menos direccionamiento indirecto significa código más rápido. La diferencia es microscópica, pero en bucles calientes o al pasar a

Blogs

Book Recommendations