Objetivos del proyecto Rust: tipos inmóviles y destructores garantizados
Los mantenedores de Rust están explorando nuevos traits a nivel de tipos como `!Move` y `!Forget` para admitir tipos inmóviles y destructores garantizados, con el objetivo de resolver problemas de larga data en async, estructuras autorreferenciales y fugas de memoria. Los comentaristas destacan cómo esto podría habilitar una concurrencia estructurada más segura (por ejemplo, tareas async scoped con limpieza garantizada) y APIs más expresivas como recursos lineales de tipo “must-use”, al tiempo que señalan serios desafíos de compatibilidad hacia atrás con traits, colecciones y el modelo `Pin` existentes. Hay un amplio entusiasmo por las capacidades que estas características podrían desbloquear, aunque acompañado de la conciencia de que el diseño es complejo, aún no está finalizado y podría implicar compromisos con propuestas alternativas como la “ergonomía de pin”.
Idea general de alto nivel
- La propuesta introduce traits como
!Move,!Forgety, eventualmente,!Destructpara:- Hacer que la inmovilidad sea una propiedad del tipo en lugar del lugar (sustituyendo gran parte de
Pin). - Garantizar que los destructores se ejecutarán (o que no sea posible filtrar memoria) para ciertos tipos.
- Sentar las bases para tipos lineales / must-move.
- Hacer que la inmovilidad sea una propiedad del tipo en lugar del lugar (sustituyendo gran parte de
Rust asíncrono y concurrencia estructurada
- La motivación principal es mejorar el async / la concurrencia estructurada:
- Hoy en día, las tareas async pueden cancelarse arbitrariamente, así que los destructores pueden no ejecutarse nunca.
- Esto bloquea tareas “scoped” totalmente seguras, análogas a los hilos scoped.
- Con
!Forget, se podría garantizar que los handles de las tareas se eliminen, haciendo que el async scoped sea seguro y ergonómico.
- También ayuda con:
- Funciones async recursivas (sin trucos con
Box::pin). - Funciones async que toman prestado de ámbitos externos en lugar de clonar.
- Funciones async recursivas (sin trucos con
Tipos inmóviles frente a Pin
Pinse considera ampliamente confuso y un “hack”:- Fija lugares y tiene semántica de
Unpiny reglas de proyección complicadas. - El código async de bajo nivel que usa
Pines poco ergonómico y depende deunsafe.
- Fija lugares y tiene semántica de
!Moveharía que la inmovilidad fuera intrínseca al tipo:- Permite referencias a sí mismo seguras y patrones complejos que
Pinno puede soportar por completo.
- Permite referencias a sí mismo seguras y patrones complejos que
- Existe una vía alternativa de “lugares fijados”/ergonomía de pin; esta propuesta es explícitamente una alternativa y pretende deprecar
Pina largo plazo.
Control de fugas y !Forget
!Forgetpretende asegurar que un destructor se ejecute antes de que el almacenamiento se reutilice.- Las fugas vía
mem::forgeto ciclos de referencias:- Se propone restringirlas requiriendo
T: ForgetparaRc/Arcy tipos similares. - Los bucles infinitos o enviar valores a otros hilos no se consideran “olvidar”, ya que el ámbito nunca termina.
- Se propone restringirlas requiriendo
Tipos lineales / must-move (!Destruct)
- Obligarían a rutas explícitas de destrucción:
- APIs como transacciones podrían requerir estáticamente exactamente una de
commit/rollback. - Se podrían evitar patrones de “rollback silencioso” o de pánico en
Drop.
- APIs como transacciones podrían requerir estáticamente exactamente una de
- La destrucción normalmente requeriría desestructuración o funciones consumidoras dedicadas en el módulo donde se define.
Compatibilidad hacia atrás e impacto en std
- La compatibilidad hacia atrás es un riesgo importante:
- Los genéricos existentes asumen implícitamente que todo es movable y droppable.
- Traits como
Iterator::ItemyDeref::Targetse vuelven problemáticos cuando algunos tipos son!Move. - Colecciones como
Vec<T>mueven elementos por naturaleza; probablemente no puedan soportar tipos!Move, o solo mediante APIs restringidas.
- Las editions ayudan, pero no resuelven todo; un diseño cuidadoso de los valores por defecto y de los bounds (
T: Movefrente aT: ?Move) es crucial y no trivial.
Relación con efectos algebraicos y C++
- Algunos ven estos traits como algo similar a efectos:
drop<T>,move<T>,forget<T>se comportan como efectos implícitos adjuntos a las funciones. - Otros argumentan que se trata principalmente de propiedades de tipo y de posibles sistemas de efectos futuros.
- Comparaciones con C++:
- Rust no está añadiendo constructores implícitos de move/copy.
- La inmovilidad y los destructores garantizados facilitan la interoperabilidad con C++ y ciertos patrones (referencias a sí mismo, grafos de objetos complejos), pero con semántica más estricta y explícita.
Estado e incertidumbre
- Esto es un objetivo del proyecto, no un cambio de lenguaje aceptado.
- Compite con el objetivo de ergonomía de pin; muchos esperan que los tipos inmóviles ganen si solo uno sobrevive, pero:
- El cambio es omnipresente y complejo.
- La semántica, la compatibilidad hacia atrás y la integración con std siguen abiertas y aún podrían descarrilar o reconfigurar significativamente el diseño.