- Ante es un diseño de lenguaje de sistemas que busca combinar la flexibilidad del conteo de referencias con la seguridad de la verificación de préstamos, evitando tanto los pánicos en tiempo de ejecución al estilo Rust como la sobrecarga de las comprobaciones de acceso exclusivo al estilo Swift
- Su mecanismo central es shape-stability y la temporary uniq conversion: permite crear préstamos mutables de forma segura sobre campos de valores con conteo de referencias, y trata los valores internos de una unión como
uniqsolo dentro de un alcance limitado Rc<RefCell<T>>de Rust puede provocar pánicos en tiempo de ejecución si se usa mal, y el borrowing system de Swift incluye comprobaciones de acceso exclusivo en tiempo de ejecución, pero Ante intenta manejar algunos casos con reglas en tiempo de compilación- Es un diseño work-in-progress, aún implementado solo en parte; como debe analizar tipos recursivamente para determinar si se puede llegar a cierto objeto, agregar campos puede convertirse en un breaking API change
- Este enfoque debilita la premisa de que shared mutable borrowing siempre es imposible, y junto con técnicas como Vale, group borrowing y Rust GhostCell está ampliando las zonas de excepción en el diseño de seguridad de memoria
La combinación que busca Ante
- Ante es un lenguaje de programación de sistemas que apunta a ser un Rust más simple, con seguridad de memoria y seguridad de hilos
- Su modelo básico es la propiedad única y la verificación de préstamos, y los valores se colocan inline en la pila o dentro de estructuras y arreglos que los contienen
- Cuando se quiere priorizar la simplicidad, se puede optar por el conteo de referencias agregando la palabra clave
shareda un tipo - La función
balancede un red-black tree que usashared type Coloryshared type RbTree tes tan corta como el ejemplo en Python y más pequeña que los ejemplos en C++ y Rust - La cuestión central es cómo manejar el préstamo mutable de datos con conteo de referencias sin el riesgo de pánico de
borrow_mut()en Rust ni las comprobaciones de acceso exclusivo en tiempo de ejecución de Swift - Ante todavía está en estado work-in-progress: algunas partes están implementadas, otras son teóricas y el diseño sigue cambiando
- El avance puede verse en el sitio de Ante y en Discord
shape-stability y múltiples referencias mutables
- Shape-stability en Ante es la idea de que “una referencia a un objetivo con stable shape siempre es válida, sin importar qué cambios ocurran en otro lugar”
- Gracias a este concepto, se pueden tener varias referencias de préstamo mutable simultáneamente a la misma estructura
- En el ejemplo
heal (healer: mut Entity) (target: mut Entity), es posible llamar aself_healpasando la mismaEntitycomo ambos argumentos para que se cure a sí misma- Aunque
healerytargetapunten a la mismaEntity, en este código no se puede destruir laEntity, así que ambas referencias siguen siendo válidas
- Aunque
- También pueden permitirse simultáneamente referencias mutables a la estructura misma, a sus campos y a los campos de sus campos
- Aunque se usen al mismo tiempo
ship: mut Spaceshipyengine_alias: mut Engine = ship.engine, se determina que durante la ejecución de la función no se destruirán nishipni suengine
- Aunque se usen al mismo tiempo
- Rust y Swift no permiten una forma en la que varias referencias
&mutapunten simultáneamente a los mismos datos
Préstamos mutables de campos de valores con conteo de referencias
- En Ante, si se antepone
shareda una definición de tipo, ese tipo pasa automáticamente a tener conteo de referencias - En el ejemplo
shared mut type Spaceship,launchmantiene unSpaceshipequivalente aRcmientras pasamut ship.engineaset_fuel - Como
launchconserva el objeto contenedorSpaceship, se puede determinar que su campoenginetambién sigue vivo - La regla general es que siempre se puede crear una referencia de préstamo
mutpara un campo de un tiposhared mut- Sin embargo, no siempre se puede crear un préstamo mutable para todos los objetivos contenidos dentro de ese campo; se necesitan reglas separadas
- Los ejemplos posteriores usan la notación más explícita
Rc Spaceshipen lugar del azúcar sintácticoshared mut type Spaceshipshared mut type Spaceshippasa a sertype Spaceship, yvar ship: Spaceshippasa a servar ship: Rc Spaceship
Dónde las uniones generan problemas de seguridad
- Las uniones pueden guardar su contenido inline, lo que reduce el seguimiento de punteros y los fallos de caché, por lo que favorecen la velocidad
- Si la
union Enginede C está dentro destruct Spaceship,StringTheoryEngineeImpulseEnginequedan ubicados dentro de la memoria deSpaceship - Esto contrasta con un enfoque como el de Java, que usa interfaces y punteros
- Si la
- El problema es que en lenguajes con seguridad de memoria es difícil soportar uniones de forma segura
- En un ejemplo donde
EngineesStringTheoryEngine(str: String)oImpulseEngine(fuel: I32), puede ocurrir un segfault cuandoshipyother_shipapuntan al mismoSpaceship- Primero se toma una referencia interna al string con
match uniq ship.engine - Luego se cambia el mismo motor a otra variante con
other_ship.engine := ImpulseEngine 0x42 - Y después, si se modifica el
stranterior, se termina usando el interior de un contenedor después de que fue destruido
- Primero se toma una referencia interna al string con
- Por eso Ante debe impedir que, cuando una referencia de préstamo mutable apunta a una unión, se cree una referencia de préstamo mutable a una de sus variantes
- Esto es lo opuesto a la regla de las estructuras
- Si se tiene una referencia
muta una estructura, se puede crear una referenciamuta un campo - Si se tiene una referencia
muta una unión, no se puede crear una referenciamutal interior de una variante
- Si se tiene una referencia
uniq y temporary uniq conversion
uniqsignifica exclusive mutable reference, es decir, referencia mutable exclusiva- Si una variable contiene un
uniq Spaceship, es la única referencia utilizable a eseSpaceship- Es un concepto similar a
&mut Spaceshipen Rust
- Es un concepto similar a
- Para manejar de forma segura el interior de una unión, Ante usa temporary uniq conversion
- La regla central es que, si dentro de un alcance específico no se usa ninguna otra referencia que pueda tener alias, se puede obtener temporalmente una referencia
uniq- En la sección
match uniq ship.engine, se accede aship.enginecomo si fuerauniq - Durante esa sección, el compilador impide usar otras variables existentes que puedan contener indirectamente un
Spaceship
- En la sección
- Rust bloquea la existencia misma de
uniqporque “podría haber otras referencias en alguna parte”, mientras que Ante permiteuniqcon la condición de no usar esas referencias en ese alcance - En este caso,
uniq Spaceshipno es realmente una referencia globalmente única, sino la única referencia utilizable dentro de ese alcance- Tiene un matiz similar a los punteros
restrictde C
- Tiene un matiz similar a los punteros
Accesos permitidos y rechazados
- Dentro del alcance de
match uniq ship.engine, acceder aother_ship: Rc Spaceshipdebería producir un error de compilación- Porque
other_ship.enginepodría ser un alias deship.engine - Y porque modificar
other_ship.enginemientras se usaship.enginepodría provocar un drop
- Porque
- Otra estructura que tenga un
Rc Spaceshipcomo campo, comoHasAShip, también se rechaza por la misma razónother.ship.enginetambién puede llegar indirectamente al mismoSpaceship
- En cambio, sí se puede usar un entero como
new_fuel: I32- Porque
I32no puede contener una referencia aSpaceship
- Porque
- Si
Spaceshipcontiene un campo comofollow_ship: Rc Spaceship, se rechaza- En ese caso, un
uniq Spaceshiptambién podría volver a ser alcanzable mediante una ruta interna propia, así que en general no se puede hacer la conversiónmut -> uniqen tipos recursivos
- En ese caso, un
Restricciones en llamadas y retornos de funciones
- La conversión
mut -> uniqtambién puede ocurrir en llamadas de funciones - Cuando
foo (var ship: Rc Spaceship) (new_res: Resonator)llama amaybe_use_resonator ship new_res, en el punto de llamadashipse convierte enuniq Spaceship- El compilador solo debe verificar si otros argumentos pueden contener referencias a
Spaceship - En el ejemplo,
Resonatorno contiene esas referencias, por lo que se permite
- El compilador solo debe verificar si otros argumentos pueden contener referencias a
- En el retorno, una referencia
uniqconvertida no puede devolverse como ununiqnormal- Porque después del retorno ya no se aplica la comprobación del compilador de “no usar variables que puedan tener alias dentro del alcance”
- En su lugar, el tipo de retorno puede especificarse como
local uniq Foo- Internamente, cuando se convierte de
mut refauniq ref, en realidad siempre se crea un local uniq - En la mayoría de los casos puede usarse como un
uniqnormal, pero al retornarlo se necesita hacerlo explícito
- Internamente, cuando se convierte de
Costos de diseño y alternativas
- Ante puede convertir una referencia con conteo de referencias como
Rc Spaceshipen ununiq Spaceshiptemporal sin errores en tiempo de ejecución - La desventaja es que el compilador debe inspeccionar recursivamente los tipos para responder preguntas como “¿se puede llegar a
SpaceshipdesdeEngine?” - Este análisis puede ser frágil
- Agregar un campo a una estructura puede convertirse en un breaking API change
- Jake, el creador de Ante, busca una mejor forma de mantener esta garantía
- Un enfoque como group borrowing y Flix references, que asigna a cada tipo mutable compartido un tipo de marca único anónimo
- Un enfoque que agrega un efecto como
Mutates 'aal modificar tipos compartidos, eliminando el análisis de tipos - Un enfoque en el que el usuario comprueba en tiempo de ejecución si dos referencias apuntan a objetos distintos, o se ofrece una comprobación unsafe envuelta en una API safe
- Un enfoque en el que el compilador rastrea valores que no están almacenados indirectamente dentro de un
Rcy por lo tanto no pueden tener alias
- También quedan como posibilidades ideas similares al iso permission de Pony, o permisos temporales que permitan mirar dentro de una estructura pero no usar referencias que apunten hacia afuera
- Lo difícil es conservar esta flexibilidad mientras se mantienen los objetivos de Ante: usabilidad, legibilidad y simplicidad
La corriente más amplia en seguridad de memoria
- Antes se consideraba que shared mutable borrowing era imposible, y se parte de la perspectiva de que Rust también fue diseñado sobre esa creencia
- Se están acumulando varias excepciones
- Ante puede obtener referencias de préstamo
uniqdesde datos shared-mutable mediante reglas de local uniqueness - Vale puede obtener referencias de préstamo inmutables desde datos shared-mutable mediante pure function
- group borrowing puede crear referencias de préstamo shared-mutable incluso cuando no hay shape-stability
- GhostCell de Rust permite que los grafos de objetos se apunten libremente entre sí, pero en un momento dado solo puede haber una referencia mutable a uno de ellos
- Ante puede obtener referencias de préstamo
- Esta corriente sugiere que podría haber principios más generales para tratar shared mutable borrowing en el diseño de seguridad de memoria
Comparación con Cell de Rust
- Los usuarios de Rust pueden preguntarse cuál es la diferencia entre poner un
Cellen un campo de una estructura y el enfoque de Ante - En el ejemplo de Ante, se puede obtener una referencia
mut Stringastatus: StringdesdeRc Spaceshipy agregar directamente" (refueling)" - Con el enfoque
Cell<String>de Rust, no se puede obtener un&mut StringdesdeRc<Spaceship>- En su lugar, se pone un valor predeterminado temporal con
status_ref.replace(String::new()) - Luego se modifica el
Stringextraído - Y al final se vuelve a colocar con
replace(status)
- En su lugar, se pone un valor predeterminado temporal con
- Este enfoque tiene varias desventajas
- Hay que crear una instancia predeterminada como
"" - Existe el riesgo de olvidar la última llamada a
replace - Existe el riesgo de que alguien lea
statusmientras el valor está reemplazado
- Hay que crear una instancia predeterminada como
- Ante permite obtener temporalmente una referencia al string
status, y mientras tanto el compilador impide que otro código acceda a él
1 comentarios
Comentarios en Lobste.rs
El hecho de que las “referencias prestadas mutables compartidas” se consideraran imposibles no fue simplemente un sacrificio que Rust aceptó para alcanzar su objetivo, sino algo mucho más cercano al objetivo central de Rust
porque el estado mutable compartido dificulta el razonamiento local sobre el código
"References are like jumps" de withoutboats trata muy bien este punto. La idea es que impedir cambios accidentales sobre estado con alias es clave para construir con facilidad sistemas que funcionen correctamente, y que las reglas de lifetimes de Rust no son solo un mecanismo para evitar el garbage collection, sino una estructura más profunda para asegurar la capacidad de razonamiento en un lenguaje que permite al mismo tiempo estado mutable y estado con alias
Se ve bastante bien
Si entendí bien, la magia de pasar de una referencia compartida a una mutable solo es posible porque está limitada a tipos que no se comparten entre hilos, y la unicidad de
Rcparece garantizarse tratando todos los objetos del mismo tipo como si estuvieran prestados con el mismo lifetimePuede que la preferencia entre una sintaxis explícita y una más natural sea cuestión de gustos, pero muestra que, si el compilador sabe más sobre
Cell, puede permitir referencias mutables hacia este de manera más flexibleY además evita la terminología confusa de Rust, donde
mutse usa como si significara no mutabilidad sino exclusividad/unicidaduniqimplica adquirir un lock?”, pero entiendo que la comparación no es conArc, sino conRcmutsignifica exclusividad/unicidad?Me pregunto si alguien tiene idea de cuál podría ser el principio unificador que se insinúa al final
También vale la pena revisar discusiones anteriores sobre publicaciones del blog de antelang.org
No entiendo bien cómo funciona esto. Parece querer decir algo como “si tienes un puntero mutable a un objeto, puedes obtener una referencia mutable a un slice de ese objeto”
Pero si es así, entonces parecería posible hacer algo como
mutref someobjext = …,mutref subfield = someobjext.a.b,someobjext.a = somethingelse, y entoncessubfieldpodría invalidarse o romperse al cambiar de valorEl artículo tenía muchas explicaciones, comparaciones con otros lenguajes y ejemplos de código, pero me costó encontrar una explicación básica de la semántica paso a paso de cómo se supone que funciona esto