Identificando el error de memoria de `collect::<Vec<_>>()` en Rust
Una optimización en `collect::<Vec<_>>()` de Rust beta reutiliza asignaciones al convertir un `Vec` en otro, lo que puede conservar silenciosamente grandes capacidades no usadas, especialmente al pasar de tipos de elemento más grandes a más pequeños, llevando el uso de memoria a niveles cientos de veces superiores a los datos vivos. Los comentaristas debaten si esto constituye una verdadera “fuga de memoria” o una consecuencia previsible de las estrategias de crecimiento y reutilización, abordando conceptos como las “space leaks”, el principio de la menor sorpresa y cómo otros lenguajes manejan la sobreasignación. Se sugieren varias soluciones y alternativas, incluyendo llamadas explícitas a `shrink_to_fit`, usar `Box<[T]>` o `Box<str>` para datos fijos, y replantear las estructuras de datos en código con uso intensivo de memoria, mientras un informe de error abierto en Rust sigue evaluando cómo debe ajustarse la optimización.
¿Esto es realmente una “fuga de memoria”?
- Muchos sostienen que esto no es una fuga real: la memoria sigue siendo propiedad de
Vecy se libera al descartarlo; es una sobreasignación. - Otros señalan que, en la cultura de los lenguajes gestionados, este tipo de sobreasignación persistente a menudo se llama coloquialmente “fuga de memoria” o “space leak”.
- Varios participantes enfatizan que en Rust, “fuga” ya tiene un significado preciso, así que usarlo de forma imprecisa resulta engañoso, aunque el impacto práctico (agotamiento de RAM) sigue siendo grave.
Qué hace la nueva optimización de collect::<Vec<_>>()
- Rust beta reutiliza una asignación de
Vecexistente al convertir medianteinto_iter().collect(), incluso a través de cambios de tipo. - Esta reutilización no está documentada y viola el modelo mental de muchos programadores de que
collectconstruye un vector nuevo y de tamaño razonable. - La intención era ahorrar asignaciones y copias, pero ahora puede conservar capacidades grandes y obsoletas.
Sorprendente explosión de capacidad (el caso 200x)
- Patrón de ejemplo: construir un
Vec<T>grande con una capacidad enorme, aplicarmap/filterpara obtener menos elementos de un tipo mucho más pequeñoU, y luegocollect::<Vec<U>>(). - Antes se hacía una nueva asignación con el tamaño adecuado; ahora se reutiliza el viejo buffer grande, por lo que cada
Vecinterno conserva una capacidad enorme sin usar. - En un
Vec<Vec<_>>con cientos de miles de vectores internos, esto se multiplica hasta consumir gigabytes de RAM desperdiciados. - Algunos lo ven como un error directo; otros dicen que el código debería llamar explícitamente a
shrink_to_fit()cuando la capacidad importa.
Comportamiento de memoria, expectativas y estándares
- Hay un fuerte debate sobre el “principio de la menor sorpresa”: muchos encuentran demasiado sorprendente la reutilización a través de cambios de tipo/tamaño, especialmente cuando
cap >> len. - Otros subrayan que la biblioteca/estándar solo promete una colección, no un comportamiento de asignación específico.
- Un desvío más amplio contrasta el modelo IFNDR/UB de C++ con las semánticas más estrictas verificadas por el compilador en Rust; Rust tiende a favorecer rechazar programas sorprendentes antes que aceptarlos silenciosamente.
Sobreasignación en el mundo real y alternativas
- Un problema similar apareció en una implementación de Aho–Corasick: muchos
Vecpequeños con estrategia de crecimiento por duplicación duplicaron el pico de memoria frente a una implementación en C. - Cambiar a estructuras estilo lista enlazada (con índices) redujo significativamente la memoria, mostrando que
Vecno siempre es la mejor opción.
Mitigaciones y tipos alternativos
- Mitigaciones sugeridas: usar
shrink_to_fit,Box<[T]>para matrices inmutables,Box<str>/Arc<str>para cadenas, o diseñar estructuras de datos que eviten muchosVecredimensionables. - Algunos proponen heurísticas en
collect(por ejemplo, reducir cuando la capacidad supere un múltiplo de la longitud), pero otros se preocupan por el coste oculto y la complejidad.