¿Salvará Rust el mundo? (I)
El problema
Los errores de programación relacionados con el uso de la memoria se llevan una buena parte de la tarta de CVE (Common Vulnerabilities and Exposures) todos los años.
Ha pasado ya mucho tiempo del, no solo clásico, sino histórico artículo de Elias Levi (más conocido entonces por Aleph One), Smashing the Stack for Fun and Profit que puso negro sobre blanco cómo se podían explotar los desbordamientos de pila en una era donde la información disponible era escasa y no precisamente fácil acceder a ella.
Muy pronto, se vio que la gestión de la memoria precisaba de una atención y pericia que no todos los programadores poseían. Cuando se crea un objeto mediante memoria dinámica (malloc
), tienes que hacer un seguimiento de su “tiempo de vida” y liberarlo (free
) cuando no sea útil para que los recursos de memoria que posee puedan ser reutilizados.
La gestión de la memoria precisa de una atención y pericia que no todos los programadores poseen.
Esta ecología es simple de establecer, pero endiabladamente compleja de poner en la práctica cuando un programa sobrepasa el adjetivo de sencillo.
Es fácil de comprender: cuando se crea un objeto, este no vive de manera aislada al resto del programa, se tiende a crear numerosas referencias (punteros) y establecer relaciones (a veces de varias capas) que finalmente llevan a preguntarnos:
- Si liberamos el objeto, ¿habrá alguna referencia que todavía siga requiriendo el uso de ese objeto?
- ¿Qué ocurre si usamos un objeto que ya ha sido liberado?
- ¿Habrá sido liberado ya? ¿Qué ocurre si libero por error dos veces un mismo objeto?
Recolectores de basura: una solución con 'peros'
A mediados de los 90 un lenguaje de programación trató, con éxito, de solucionar el problema con el software creado por lenguajes con gestión manual de la memoria: C y C++ principalmente.
Sin sorpresas, fue Java. El lenguaje de la extinta compañía Sun Microsystems (nota curiosa: recordemos que hay un IDE para Java llamado… Eclipse), aunaba el auge incipiente de la orientación a objetos y añadía conceptos que no eran nuevos, pero estaban bien ejecutados: una máquina virtual y un recolector de memoria, entre otros.

Java supuso un salto de calidad y cantidad. Los programadores podrían (casi) abstraerse de la gestión de memoria y eran más productivos. Además, dado que Java era más fácil de aprender (o más bien, de dominar) que C y C++, reducía el nivel de acceso de nuevos programadores. Esto redundaba en más asequibilidad para las empresas que, a su vez, podían realizar proyectos de mayor complejidad en menos tiempo.
El lenguaje Go, también conocido como Golang, nació precisamente por la frustración del uso de C++ y … Java.
Aun hoy, otros lenguajes de programación han emulado esta iniciativa. El ejemplo más claro el lenguaje Go o también conocido como Golang. Nacido precisamente por la frustración del uso de C++ y … Java.
En ambos, el uso de recolectores de memoria borra de un escobazo el problema que crea la gestión manual, algo que promueve la sencillez del lenguaje y, de nuevo, baja la barrera de entrada de nuevos programadores.
Solo existe un inconveniente, y no es precisamente uno que pueda ser pasado por alto: la gestión automática de la memoria posee un coste en rendimiento.
El gran 'pero'
Aunque cada vez se avance más en nuevos algoritmos de gestión de memoria y las capacidades de desarrollo de técnicas permitan rascar nanosegundos de rendimiento, el uso de recolectores de memoria tiene un peaje que cobrarse: durante la recolección de “basura” (memoria no referenciada o utilizada) el mundo se detiene.
En efecto, cuando toca recolectar memoria para que sea reciclada el programa detiene lo que está haciendo, llama a un procedimiento del recolector y libera memoria de forma rutinaria. Haga lo que haga el programa en ese momento su mundo se detiene una fracción de segundo y se dedica a limpiar la memoria. Una vez termina, vuelve a lo que estaba haciendo como si nada hubiese ocurrido.
Para las empresas y en una escala lo suficientemente grande, este Stop the world se traduce en un coste agregado a la factura de servidores.
Hay decenas de artículos, casi todos enumeran las mismas razones o quejas: el coste del recolector de basura.
Aunque pueda parecer minúsculo, los picos de respuesta acumulados en grandes aplicaciones con mucha demanda se traducen en una factura económica al final de la cadena. Da igual lo bien que programes y los algoritmos y estructuras de datos que utilices: el mundo se detiene para recolectar la basura y no vuelve a andar hasta dejar la memoria como una patena.
El dilema
De modo que,
- Por un lado, tenemos los lenguajes que poseen un gran rendimiento y excepcional ejecución, pero que precisan de personal debidamente cualificado (y aun así…) y herramientas que mitiguen los errores de gestión.
- Por otro lado, están los lenguajes que aumentan la productividad, reducen drásticamente los errores de gestión de memoria (aunque no nos olvidemos de los NullPointerException), pero por el contrario poseen una pequeña traba que los hace más tragones en cuanto a recursos computacionales.
Es un auténtico dilema en algunos casos, aunque también se hace evidente dependiendo de la naturaleza del proyecto (no vas a escribir un módulo del kernel Linux en Perl ni vas a implementar una aplicación web en C, como se hacía en los 90).
Salvando los extremos, las opciones pasan por meditar y calibrar qué sacrificamos y qué ventajas son más oportunas.
La tercera vía
Pero, ¿Y si hubiera un término medio? ¿Y si pudiéramos encontrar una forma de deshacernos de las pausas del recolector de basura, pero a su vez no tener que gestionar la memoria manualmente?
¿Cómo lo hacemos? Lo vemos en el próximo artículo.
Fotografía principal: Mark Fletcher Brown / Unsplash