Regresar
702

Memory safety: ownership de Rust vs garbage collection vs manual

Actualizado: 15/09/2026

Durante años, las agencias de ciberseguridad de varios países repitieron el mismo dato con insistencia: alrededor de dos tercios de las vulnerabilidades graves en software escrito en C y C++ vienen de errores de manejo de memoria. No de lógica de negocio, no de configuración: de leer o escribir donde no se debía.

Ese dato explica por qué este tema, que durante mucho tiempo fue una discusión académica, se convirtió en una recomendación oficial de política pública. Y también explica por qué, para la mayoría de los proyectos web, la decisión ya está tomada sin que nadie la haya pensado.

Qué significa gestionar la memoria

Todo programa pide memoria para guardar datos mientras trabaja, y en algún momento tiene que devolverla. La pregunta que distingue a los lenguajes es quién decide cuándo.

Tres formas de liberar memoria comparadas: en la gestión manual, como en C, la libera el programador, con control total pero donde un olvido produce una fuga o un fallo grave; con recolector, como en Go, Java o JavaScript, la libera el entorno cuando decide, sin tener que pensar en ello pero con pausas y más memoria usada; con ownership, como en Rust, la libera el compilador al terminar el ámbito, con seguridad sin recolector y una curva de aprendizaje empinada
Para la mayoría de las aplicaciones web, el recolector es la opción razonable y el coste no se nota.

Las tres respuestas son un intercambio entre control, seguridad y comodidad, y ninguna gana en las tres. Lo interesante es que la tercera opción, la de Rust, rompió un compromiso que durante décadas se consideró inevitable: la idea de que para tener seguridad había que pagar un recolector.

Los errores que la gestión manual permite

Conviene conocerlos por su nombre, porque aparecen en los avisos de seguridad y porque explican qué protegen las otras dos opciones.

  • Uso tras liberar. Se libera un bloque de memoria y después se sigue usando. Si otra parte del programa ya lo reutilizó, se leen o escriben datos ajenos. Es la fuente de muchas vulnerabilidades explotables.
  • Desbordamiento de búfer. Se escribe más allá del final de un espacio reservado, pisando lo que haya a continuación. Durante décadas fue la vía clásica para ejecutar código ajeno.
  • Doble liberación. Se libera dos veces el mismo bloque, corrompiendo las estructuras internas que gestionan la memoria.
  • Fuga. Se reserva memoria y nunca se devuelve. No es un fallo de seguridad directo, pero un servidor que pierde un poco en cada petición acaba muriendo tras días funcionando.

Los tres primeros son los peligrosos, y tienen algo en común: el programa no falla de inmediato. Sigue funcionando con datos corruptos, a veces durante mucho tiempo, hasta que alguien encuentra la forma de aprovecharlo.

El recolector: la opción que ya estás usando

Si trabajas con JavaScript, Python, PHP, Go, Java o C#, la memoria la gestiona un recolector. El programa pide memoria libremente y el entorno detecta de vez en cuando qué ya no se usa y lo libera.

Los tres errores peligrosos de antes simplemente no pueden ocurrir: no puedes usar algo liberado porque nada se libera mientras alguien lo referencie, y no puedes escribir fuera de un espacio porque el lenguaje lo comprueba.

El precio tiene dos partes. La primera es memoria: el recolector necesita margen para trabajar, y un programa con recolector usa bastante más memoria que el equivalente gestionado a mano. La segunda son las pausas: mientras el recolector revisa, el programa se detiene brevemente. Los recolectores modernos han reducido esas pausas a milisegundos o menos, y para una aplicación web son imperceptibles.

Donde sí se notan es en sistemas con requisitos estrictos de tiempo: audio en directo, juegos, sistemas de control. Fuera de esos casos, el recolector es una elección sensata que se paga con memoria barata.

Las fugas también existen con recolector

Una creencia frecuente es que con recolector no hay fugas de memoria. Las hay, y son de las que más cuesta encontrar porque no parecen fugas.

El recolector libera lo que ya nadie referencia. Si algo sigue referenciado sin necesidad, nunca se libera. Los casos típicos son una caché en memoria que crece sin límite, oyentes de eventos que se registran y nunca se quitan, o una lista global donde se van añadiendo cosas.

# Node: vigilar si la memoria crece sin volver a bajar
node --inspect servidor.js
# en el navegador, abrir chrome://inspect y comparar dos instantáneas del heap

# Ver la memoria residente del proceso a lo largo del tiempo
while true; do ps -o rss= -p $(pgrep -f servidor); sleep 30; done

La señal de alerta es un gráfico en diente de sierra que, en vez de volver a la misma base tras cada recolección, va subiendo poco a poco. Eso indica algo que se acumula, y ningún recolector lo va a arreglar.

Ownership: seguridad sin recolector

Rust resuelve el problema de otra forma. No hay recolector y tampoco se libera a mano: cada valor tiene un único dueño, y cuando el dueño deja de existir, la memoria se libera sola. El compilador comprueba estas reglas antes de generar el programa.

// Rust: el compilador impide usar un valor después de moverlo
fn main() {
    let texto = String::from("pedido 8841");
    let otro = texto;            // la propiedad pasa a 'otro'
    println!("{}", texto);       // error de compilación: 'texto' ya no es dueño
}

El resultado es que los errores de memoria que en C aparecen en producción, en Rust aparecen como errores de compilación. No se puede generar un programa que use algo liberado.

El precio es la curva de aprendizaje. Las reglas obligan a pensar la estructura de los datos de una forma distinta, y durante las primeras semanas el compilador rechaza código que en otro lenguaje funcionaría. Esa fricción es real y conviene no subestimarla.

Las tres, comparadas

ManualRecolectorOwnership
Errores de memoria peligrososPosiblesImposiblesImposibles
Cuándo se detectanEn producción, si acasoNo aplicaAl compilar
Uso de memoriaMínimoAltoMínimo
PausasNingunaBrevesNinguna
Fugas por referencias olvidadasSíSíRaras
Curva de aprendizajeMedia, con trampasBajaAlta
Dónde encajaCódigo heredado, embebidoCasi toda la webSistemas, rendimiento crítico

La fila de la curva de aprendizaje es la que más pesa en un equipo pequeño, y la que menos aparece en los argumentos a favor de cambiar de lenguaje.

Cuándo tiene sentido el cambio

Para una aplicación web normal, la conclusión es directa: el lenguaje con recolector que ya usas es la opción correcta, y cambiar por motivos de memoria no resuelve ningún problema que tengas.

Rust empieza a tener sentido en casos concretos: una pieza que procesa grandes volúmenes y donde el uso de memoria encarece la factura, un componente donde las pausas importan, una herramienta de línea de comandos que debe arrancar al instante, o código que manipula datos no confiables a bajo nivel, como un analizador de formatos.

Y hay un camino intermedio muy razonable: escribir en Rust solo la pieza crítica y llamarla desde el lenguaje principal. Varios ecosistemas lo permiten, y así el coste de aprendizaje queda acotado a una parte pequeña del sistema.

Lo que no tiene sentido es reescribir una aplicación entera porque la memoria es más segura, cuando esa aplicación ya estaba escrita en un lenguaje con recolector que tenía exactamente esa misma garantía.

Seguro en memoria no significa seguro

Conviene poner un límite claro a lo que garantiza un lenguaje con memoria segura, porque la expresión se entiende a menudo como algo más amplio de lo que es.

Un lenguaje con recolector o con ownership impide que un programa lea o escriba donde no debe dentro de su propia memoria. No impide ninguno de los fallos que dominan las listas de vulnerabilidades web: control de acceso roto, inyección en consultas, validación ausente, secretos expuestos o dependencias comprometidas.

Una aplicación escrita íntegramente en un lenguaje seguro en memoria puede dejar que cualquier usuario lea los datos de otro cambiando un número en la dirección. Ese error no tiene nada que ver con la memoria y es mucho más frecuente.

La consecuencia práctica es que la seguridad de memoria resuelve una categoría concreta de problemas, importante en software de sistemas, y deja intactas las que más afectan a un proyecto web. Es una razón para no empezar un servicio nuevo en C, no un sustituto de todo lo demás.

El código que no escribiste también cuenta

Hay un matiz que conviene conocer: aunque tu aplicación esté en un lenguaje seguro, buena parte del software que ejecuta no lo está.

El intérprete de Python, el motor de JavaScript, la librería de compresión, el analizador de imágenes y el propio servidor web suelen estar escritos en C o C++. Cuando aparece una vulnerabilidad de memoria en una de esas piezas, tu aplicación la hereda, por muy segura que sea su propia capa.

Por eso actualizar el entorno de ejecución y las librerías nativas importa tanto como el lenguaje elegido. Los avisos de seguridad que más afectan a proyectos web vienen, con frecuencia, de esas dependencias de bajo nivel.

# Versión del intérprete y del sistema, que también reciben parches de seguridad
node --version && python3 --version && openssl version

# Paquetes del sistema con actualizaciones pendientes (Debian/Ubuntu)
apt list --upgradable 2>/dev/null | grep -iE "openssl|libssl|zlib|libxml|imagemagick"

# Dependencias con vulnerabilidades conocidas en un proyecto Node
npm audit --omit=dev

Esa última comprobación, periódica, protege más a una aplicación web típica que cualquier decisión sobre el modelo de memoria del lenguaje.

Qué se le escapa a una IA con esto

En lenguajes con recolector, el código generado casi nunca tiene errores de memoria peligrosos, porque el lenguaje los impide. Donde sí aparecen problemas es en las fugas por referencias: cachés en memoria sin límite de tamaño y oyentes que se registran en cada petición sin quitarse nunca.

En Rust ocurre algo distinto y conviene conocerlo: el código generado a veces esquiva al compilador con copias innecesarias o con bloques marcados como inseguros para que compile. Ambas cosas anulan justo la ventaja por la que se eligió el lenguaje.

Las frases que cambian la respuesta son preguntar si alguna estructura crece sin límite mientras el programa corre, y en Rust pedir explícitamente que no se use código inseguro y que se justifique cada copia.

Y ante una propuesta de reescribir algo en otro lenguaje por rendimiento o seguridad de memoria, la pregunta útil es qué problema medido resuelve. Si la respuesta es genérica, el cambio probablemente no compensa el coste.


Comparativa intermedio