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, !Forget y, eventualmente, !Destruct para:
    • 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.

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.

Tipos inmóviles frente a Pin

  • Pin se considera ampliamente confuso y un “hack”:
    • Fija lugares y tiene semántica de Unpin y reglas de proyección complicadas.
    • El código async de bajo nivel que usa Pin es poco ergonómico y depende de unsafe.
  • !Move haría que la inmovilidad fuera intrínseca al tipo:
    • Permite referencias a sí mismo seguras y patrones complejos que Pin no puede soportar por completo.
  • Existe una vía alternativa de “lugares fijados”/ergonomía de pin; esta propuesta es explícitamente una alternativa y pretende deprecar Pin a largo plazo.

Control de fugas y !Forget

  • !Forget pretende asegurar que un destructor se ejecute antes de que el almacenamiento se reutilice.
  • Las fugas vía mem::forget o ciclos de referencias:
    • Se propone restringirlas requiriendo T: Forget para Rc/Arc y tipos similares.
    • Los bucles infinitos o enviar valores a otros hilos no se consideran “olvidar”, ya que el ámbito nunca termina.

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.
  • 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::Item y Deref::Target se 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: Move frente a T: ?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.