18 puntos por xguru 2024-11-04 | 12 comentarios | Compartir por WhatsApp

Lo que salió bien

  • La reescritura se hizo en pasos pequeños (gradual, stop-and-go), funcionó bien y el nuevo código quedó más fácil de leer y entender
  • Al tener una visión de todo el código, surgieron oportunidades de optimización de rendimiento
  • Se eliminó alrededor de entre 1/3 y 1/2 del código sin usar. Los lenguajes de programación modernos como Rust o Go detectan mejor el código muerto y se lo señalan al desarrollador
  • Ya no hay preocupación por accesos fuera de rango ni por overflow/underflow
  • El framework de pruebas integrado es muy útil
  • Fue una alegría poder eliminar los archivos de CMake

Lo que no salió bien

Todavía hay que rastrear comportamiento indefinido

  • Al hacer una reescritura gradual de C/C++ a Rust, hubo que usar muchos punteros sin procesar y bloques unsafe{}
  • Las reglas de Rust siguen aplicando dentro de unsafe, pero como el compilador no las verifica, el comportamiento indefinido puede aparecer con facilidad
  • Dentro de unsafe, es fácil romper la regla de varios punteros de solo lectura XOR un puntero mutable
  • Miri fue un salvavidas al detectar esto

Miri no siempre funciona y todavía hay que usar Valgrind

  • Si se usan bibliotecas con partes escritas en C o ensamblador, como las bibliotecas criptográficas, Miri no funciona
  • Hay mucho código unsafe que Miri no revisa
  • Algunas pruebas tuvieron que ejecutarse con valgrind

Todavía hay que rastrear fugas de memoria

  • Un patrón común en las API de C es asignar memoria en MYLIB_init() y liberarla en MYLIB_release(), pero es fácil olvidar llamar a MYLIB_release
  • Los desarrolladores de Rust querrían crear objetos envoltorio con RAII, pero en las pruebas que usan la API de C no se puede aprovechar esa funcionalidad
  • En lógica compleja, es difícil llamar siempre a la función de limpieza. En C esto se resuelve con goto, pero Rust no lo soporta
  • Se resolvió con el crate defer, pero al borrow checker no le gustó

La compilación cruzada no siempre funciona

  • Igual que con Miri, si se usan bibliotecas con partes implementadas en C o ensamblador, cargo build --target=... no funciona de inmediato

Cbindgen no siempre funciona

  • Cbindgen se usa mucho para generar encabezados de C desde una base de código Rust, pero tiene limitaciones o bugs

ABI inestable

  • Tipos útiles de la biblioteca estándar como Option no tienen un ABI estable, así que hay que duplicarlos manualmente con la anotación repr(C)

Falta de soporte para asignadores de memoria personalizados

  • Muchas bibliotecas en C permiten que el usuario proporcione un asignador en tiempo de ejecución. En Rust solo se puede elegir un asignador global en tiempo de compilación
  • Los problemas de liberación de recursos pueden resolverse con un asignador de arena, pero en Rust no es idiomático ni se integra con la biblioteca estándar

Complejidad

  • Hay que usar cosas como UnsafeCell, RefCell, MaybeUninit y Pin para manejar FFI, lo que eleva mucho la complejidad
  • Rust puro ya es complejo, y si además se suma una capa FFI, se vuelve una bestia
  • Incluso hubo desarrolladores que rechazaron trabajar en esta base de código por la complejidad de Rust

Conclusión

  • En general hubo satisfacción con la reescritura en Rust, pero en algunas áreas resultó decepcionante y requirió mucho más esfuerzo del esperado
  • Rust con mucha interacción con C se siente como un lenguaje completamente distinto a usar Rust puro. Hay mucha fricción y muchas trampas. Muchos de los problemas de C++ que Rust dice resolver en realidad no quedan resueltos en absoluto
  • Hay un profundo agradecimiento hacia los desarrolladores de Rust, Miri y cbindgen. Han hecho un trabajo enorme. Aun así, el lenguaje y las herramientas para hacer mucho C FFI se sienten inmaduros, casi como si aún estuvieran antes de la v1.0
  • Si mejoran en el futuro la ergonomía de unsafe, la biblioteca estándar, la documentación, las herramientas y el ABI inestable, la experiencia podría ser mucho más agradable
  • Parece que Microsoft y Google también percibieron todo esto, por eso están invirtiendo dinero real en esta área
  • Si todavía no conoces Rust, conviene que tu primer proyecto sea en Rust puro y que te mantengas alejado del tema FFI
  • Al principio se consideró usar Zig u Odin para esta reescritura, pero no se quería usar un lenguaje pre-v1.0 en una base de código corporativa de producción. Ahora queda la duda de si la experiencia realmente habría sido peor que con Rust. Probablemente el modelo de Rust simplemente no encaja con el modelo de C (o de C++), así que al usarlos juntos la fricción es demasiado fuerte
  • Si en el futuro hubiera que hacer un trabajo parecido, Zig sería una opción muy seria. Cada vez que alguien diga "simplemente reescríbanlo en Rust", muéstrenle este texto y pregúntenle si cambió de opinión

12 comentarios

 
bus710 2024-11-05

Aunque Zig sigue en pre-v1, puede usar muchas librerías de C, así que resulta más útil de lo que parece. Para agregar algo a un proyecto basado en C que ya está funcionando, Zig puede ser una mejor opción que Rust.

 
ahwjdekf 2024-11-04

Cuando estuve revisando Rust, en el momento en que vi la palabra clave unsafe, me dio una sensación inquietante...

 
jkliop890 2024-11-04

Creo que Rust no puede resolver los problemas crónicos que tiene C++. Y lo digo más desde una perspectiva práctica que sintáctica.

Las razones serían:

  1. Ya hay demasiados entornos de producción que usan C/C++. Y funcionan de forma estable. Además, en la mayoría de los casos ni siquiera intentan portar eso a Rust.

  2. Para empezar, el hardware no está diseñado dando por sentado el conteo de referencias. En muchos casos, C/C++ se usa para controlar con alta velocidad el hardware, el SO, los drivers y la capa binaria, pero para dar soporte a Rust, al final los desarrolladores de bajo nivel tienen que gestionar directamente el ciclo de vida de los recursos usando unsafe, y eso también tiene un costo alto.

Creo que la experiencia del autor es más importante que el valor potencial del lenguaje o las discusiones teóricas.
Me da la impresión de que, en los ámbitos donde se necesita un lenguaje del nivel de C/C++, el nivel de gestión de recursos es algo demasiado ambiguo como para reemplazarlo con Rust.

 
cosine20 2024-11-04

Este artículo también se lanzó malentendiendo Rust.
Si ves el contenido, parece una biblioteca que tiene que comunicarse con frecuencia con el exterior de Rust, y en ese punto ya es inevitable que se ensucie... Desde el principio, entre los lenguajes nativos no hay ninguno que no sea sucio, y Rust lo que hace es envolver eso de forma segura a nivel de lenguaje, así que mientras más puntos de contacto haya con el exterior del lenguaje, más se pierden sus ventajas.

Muchos de los problemas de C++ que Rust dice haber resuelto en realidad no se resuelven en absoluto

Hasta cierto punto es verdad, pero en el entorno de desarrollo del texto original era inevitable que no se resolvieran, y creo que el problema fue acercarse a Rust como si fuera una cura para todo.

 
kotlinc 2024-11-04

Sentí que significaba que, como en el proceso de cambiar gradualmente de C/C++ a Rust inevitablemente hay que usar unsafe, no tiene sentido cambiarse a Rust; y que, en vez de hacer un cambio gradual a Rust, elegiría Zig. ¿En qué parte del texto dice que es una biblioteca que necesita comunicarse con frecuencia con el exterior de Rust?

 
cosine20 2024-11-04

Usar FFI significa, en esencia, comunicarse con algo fuera de Rust.
Y por el contenido del texto, parece que no se trata simplemente de intercambiar algún estado o datos sencillos, sino de una interacción compleja entre el interior y el exterior.

 
kotlinc 2024-11-05

¿No es inevitable usar FFI si se quiere reemplazar gradualmente con Rust una biblioteca escrita en C? Supongo que habría que ir sustituyendo pequeñas partes del programa por Rust y manejar el resto en C mediante FFI; ¿a eso se refería con comunicarse con el exterior? Si es así, creo que es natural que el autor original se sienta escéptico con Rust. A menos que se reemplace todo el código de una sola vez, no se obtienen las ventajas de Rust, así que por eso recomendaría Zig.

 
cosine20 2024-11-05

^-^

 
savvykang 2024-11-04

Como las partes unsafe se marcan explícitamente en el código fuente, esperaba que fuera útil porque, salvo que se usen bloques unsafe desde el punto de entrada del programa, se puede identificar todo el alcance del impacto de la FFI, pero parece que al autor no le resultó tan convincente.

 
carnoxen 2024-11-04

Desde el momento en que usaron FFI, el diseño seguro básicamente ya se había ido por la borda.

 
cosine20 2024-11-04

Así es.

 
kohs100 2024-11-04

Sí, vaya... lo escriben con toda seguridad diciendo que estaba lleno de unsafe y aun así resulta que no se resolvió...