Centrándose en la propiedad

Nota traductor: el registro está fechado el 13 de mayo de 2014, por lo que algunos detalles, incluido el código fuente, pueden no corresponder al estado actual de las cosas. La respuesta a la pregunta de por qué se necesita la traducción de una publicación tan antigua será el valor de su contenido para comprender uno de los conceptos fundamentales del lenguaje Rust, como la fluidez.


Con el tiempo, me convencí de que sería mejor abandonar la distinción entre variables locales mutables e inmutables en Rust. Al menos muchas personas son escépticas sobre este tema. Quería expresar mi posición en público. Voy a dar varios motivos: filosóficos, técnicos y prácticos, así como a la defensa principal del sistema actual. (Nota: vi esto como Rust RFC, pero decidí que el tono es mejor para una publicación de blog y no tengo tiempo para reescribirlo ahora).


Explicación


Escribí este artículo con bastante decisión y creo que la línea que defiendo será correcta. Sin embargo, si no terminamos de admitir el sistema actual, esto no será un desastre o algo así. Tiene sus ventajas, y en general me parece bastante agradable. Solo creo que podemos mejorarlo.


En una palabra


Me gustaría eliminar la distinción entre variables locales inmutables y mutables y cambiar el nombre de los &mut pointers a &my , &only o &uniq (no me importa). Si solo no hubiera una palabra clave mut .


Motivo filosófico


La razón principal por la que quiero hacer esto es porque creo que esto hará que el lenguaje sea más consistente y fácil de entender. Esencialmente, esto nos reorientará de hablar de mutabilidad a hablar de usar alias (lo que llamaré "compartir", ver más abajo).


La variabilidad se convierte en una consecuencia de la unicidad: "Siempre puede cambiar todo a lo que tenga acceso exclusivo. Los datos compartidos generalmente son inmutables, pero si lo necesita, puede cambiarlos utilizando algún tipo de tipo de Cell ".


En otras palabras, con el tiempo, me quedó claro que surgen problemas con las carreras de datos y la seguridad de la memoria cuando se usan alias y mutabilidad. Un enfoque funcional para resolver este problema es eliminar la mutabilidad. El enfoque de Rust sería eliminar el uso de alias. Esto nos da una historia que se puede contar y que nos ayudará a resolverlo.


Una nota sobre terminología: creo que deberíamos referirnos al uso de alias como separación ( nota del traductor: en adelante, en todas partes en lugar de "aliasing" se usa "compartir" en el significado de "separación" o "propiedad compartida", ya que tampoco "uso de alias", ni "seudonimización" da una idea de lo que está en juego ). En el pasado, evitamos esto debido a sus referencias multiproceso. Sin embargo, si implementamos los planes de paralelización de datos que propuse, esta connotación no es del todo inapropiada. De hecho, dada la estrecha relación entre la seguridad de la memoria y la carrera de datos, realmente quiero promover esta connotación.


Motivo educativo


Creo que las reglas actuales son más difíciles de entender de lo que deberían ser. No es obvio, por ejemplo, que &mut T no implica ninguna propiedad compartida. Además, la designación &mut T implica que &T no implica ninguna mutabilidad, lo que no es del todo exacto, debido a tipos como Cell . Y es imposible acordar cómo llamarlos (los "enlaces mutables / inmutables" son los más comunes, pero esto no es del todo correcto).


En contraste, un tipo como &my T o &only T parece simplificar la explicación. Este es un enlace único : naturalmente, no puede forzar a dos de ellos a señalar al mismo lugar. Y la mutabilidad es algo ortogonal: proviene de la singularidad, pero también es válido para las células. Y el tipo &T es justo lo contrario, un enlace compartido . RFC PR # 58 proporciona una serie de argumentos similares. No los repetiré aquí.


Motivo práctico


Actualmente, existe una brecha entre los punteros prestados, que pueden ser compartidos o mutables + únicos, y las variables locales que siempre son únicas, pero pueden ser mutables o inmutables. El resultado final de esto es que los usuarios deben publicar anuncios mut sobre cosas que no son directamente editables.


Las variables locales no se pueden modelar utilizando referencias


Este fenómeno ocurre porque los enlaces no son tan expresivos como las variables locales. En general, esto impide la abstracción. Déjame darte algunos ejemplos para explicar lo que quiero decir. Imagine que tengo una estructura de entorno que almacena un puntero a un contador de errores:


 struct Env { errors: &mut usize } 

Ahora puedo crear instancias de esta estructura (y usarlas):


 let mut errors = 0; let env = Env { errors: &mut errors }; ... if some_condition { *env.errors += 1; } 

OK, ahora imagina que quiero separar el código que modifica env.errors en una función separada. Podría pensar que, dado que la variable env no se declara como mutable, puedo usar el enlace inmutable & :


 let mut errors = 0; let env = Env { errors: &mut errors }; helper(&env); fn helper(env: &Env) { ... if some_condition { *env.errors += 1; //  } } 

Pero esto no es así. El problema es que &Env es un tipo de propiedad compartida ( nota del traductor: como usted sabe, puede existir más de una referencia de objeto inmutable a la vez ) y, por env.errors tanto, env.errors aparece en un espacio que permite que el objeto env se env.errors separado. Para que este código funcione, tengo que declarar env como mutable y usar el enlace &mut ( nota del traductor: &mut ) para decirle al compilador que env es único en su propiedad, ya que solo puede existir una referencia de objeto mutable a la vez y se excluye la carrera de datos, pero mut porque no puedes crear una referencia mutable a un objeto inmutable ):


 let mut errors = 0; let mut env = Env { errors: &mut errors }; helper(&mut env); 

Este problema surge porque sabemos que las variables locales son únicas, pero no podemos poner este conocimiento en una referencia prestada sin hacerlo mutable.


Este problema ocurre en varios otros lugares. Hasta ahora hemos escrito sobre esto de diferentes maneras, pero sigo obsesionado por la sensación de que estamos hablando de un descanso, que simplemente no debería ser.


Verificación de tipo de cierres


Tuvimos que sortear esta limitación en el caso de los cierres. Los cierres están abiertos en su mayoría en estructuras como Env , pero no del todo. Esto se debe a que no quiero exigir que las variables locales se declaren mut si se usan a través de &mut en un cierre. En otras palabras, tome un código, por ejemplo:


 fn foo(errors: &mut usize) { do_something(|| *errors += 1) } 

Una expresión que describe el cierre en realidad creará una instancia de la estructura Env :


 struct ClosureEnv<'a, 'b> { errors: &uniq &mut usize } 

Mira el enlace &uniq . Esto no es algo que el usuario final pueda ingresar. Significa un puntero "único pero no necesariamente mutable". Esto es necesario para pasar la verificación de tipo. Si el usuario intentara escribir esta estructura manualmente, tendría que escribir &mut &mut usize , lo que a su vez requeriría que el parámetro de errors se declare como mut errors: &mut usize .


Cierres y procedimientos desempaquetados


Predigo que esta restricción es un problema para los cierres desempaquetados. Permítanme detallar el diseño que estaba considerando. Básicamente, la idea era que la expresión || es equivalente a algún nuevo tipo estructural que implementa uno de los rasgos Fn :


 trait Fn<A, R> { fn call(&self, ...); } trait FnMut<A, R> { fn call(&mut self, ...); } trait FnOnce<A, R> { fn call(self, ...); } 

El tipo exacto se seleccionará de acuerdo con el tipo esperado, a partir de hoy. En este caso, los consumidores de cierres pueden escribir una de dos cosas:


 fn foo(&self, closure: FnMut<usize, usize>) { ... } fn foo<T: FnMut<usize, usize>>(&self, closure: T) { ... } 

Nosotros ... probablemente queremos arreglar la sintaxis, quizás agregar azúcar como FnMut(usize) -> usize , o guardar | usize | -> usize, etc. No es tan importante, es importante que pasemos el cierre por valor . Tenga en cuenta que de acuerdo con las reglas actuales de DST (Tipos de tamaño dinámico) está permitido pasar un tipo por valor como argumento al FnMut<usize, usize> , por lo tanto, el argumento FnMut<usize, usize> es un DST válido y no es un problema.


Aparte : este proyecto no está completo, y describiré todos los detalles en un mensaje separado.


El problema es que se requiere un enlace &mut para llamar a un cierre. Dado que el cierre se pasa por valor, los usuarios nuevamente tendrán que escribir mut donde parece fuera de lugar:


 fn foo(&self, mut closure: FnMut<usize, usize>) { let x = closure.call(3); } 

Este es el mismo problema que en el ejemplo Env anterior: lo que realmente sucede aquí es que el FnMut solo quiere un enlace único , pero como no es parte del sistema de tipos, solicita un enlace mutable .


Ahora quizás podamos solucionar esto de diferentes maneras. Una opción que podríamos hacer es || la sintaxis no se expandiría a un "cierto tipo estructural", sino más bien a un "tipo estructural o un puntero a un tipo estructural, según lo dictado por la inferencia de tipos". En este caso, la persona que llama podría escribir:


 fn foo(&self, closure: &mut FnMut<usize, usize>) { let x = closure.call(3); } 

No quiero decir que este es el fin del mundo. Pero este es otro paso adelante en las crecientes distorsiones que debemos atravesar para mantener esta brecha entre las variables locales y las referencias.


Otras partes API


No hice un estudio exhaustivo, pero, por supuesto, esta diferencia se extiende a otra parte. Por ejemplo, para leer desde Socket , necesito un puntero único, por lo que tengo que declararlo mutable. Por lo tanto, a veces esto no funciona:


 let socket = Socket::new(); socket.read() // :    

Naturalmente, según mi sugerencia, ese código funcionaría bien. Seguiría recibiendo un mensaje de error si intentara leer &Socket , pero luego leería algo como "es imposible crear un enlace único a un enlace compartido", lo que personalmente considero más comprensible.


¿Pero no necesitamos mut por seguridad?


No, para nada Los programas Rust serían igualmente buenos si declarara todos los enlaces como mut . El compilador es perfectamente capaz de rastrear qué variables locales están cambiando en un momento dado, precisamente porque son locales para la función actual. Lo que realmente le importa al sistema de tipos es la singularidad.


El significado que veo en las reglas actuales de aplicación de mut , y no negaré que tiene valor, es principalmente que ayudan a declarar la intención. Es decir, cuando leo el código, sé qué variables se pueden reasignar. Por otro lado, también paso mucho tiempo leyendo código C ++ y, francamente, nunca me he dado cuenta de que este es un gran obstáculo. (Lo mismo ocurre con el tiempo que pasé leyendo código en Java, JavaScript, Python o Ruby).


También es cierto que a veces encuentro errores porque declaró la variable como mut y olvidé cambiarla. Creo que podríamos obtener beneficios similares con otras comprobaciones más agresivas (por ejemplo, ninguna de las variables utilizadas en la condición del bucle cambia en el cuerpo del bucle). Personalmente, no recuerdo haber enfrentado la situación opuesta: es decir, si el compilador dice que algo debe ser mutable, básicamente significa que olvidé la palabra clave mut alguna parte. (Piense: ¿cuándo fue la última vez que respondió a un error del compilador sobre un cambio no válido haciendo algo más que reestructurar el código para que el cambio sea válido?)


Alternativas


Veo tres alternativas al sistema actual:


  1. La que presenté en la que simplemente descartas la "mutabilidad" y solo rastreas la singularidad.
  2. Uno donde tiene tres tipos de referencia: & , &uniq y &mut . (Como escribí, este es realmente el sistema de tipos que tenemos hoy, al menos desde el punto de vista de un verificador de préstamos).
  3. Una opción más rigurosa, en la que las variables que no son mut siempre se consideran separadas. Esto significaría que tendría que escribir:


     let mut errors = 0; let mut p = &mut errors; // ,  `p`   ,  `mut`. *p += 1; 

    mut declarar p como mut , porque de lo contrario la variable se consideraría separada, aunque sea una variable local y, por lo tanto, no se permite cambiar *p . Lo que es extraño en este esquema es que la variable local NO permite una propiedad separada, y lo sabemos con certeza, porque cuando intentas crear su alias, se moverá, el destructor comenzará en él, etc. Es decir, todavía tenemos el concepto de "propiedad", que es diferente de "no permite la propiedad separada".


    Por otro lado, si describimos este sistema, diciendo que la mutabilidad se hereda a través de punteros &mut , sin siquiera tartamudear sobre la propiedad compartida, esto podría tener sentido.



De estos tres, definitivamente prefiero el número 1. Es el más simple, y ahora estoy más interesado en cómo podemos simplificar Rust preservando su carácter. De lo contrario, doy preferencia a la que tenemos ahora.


Conclusión


Básicamente, encuentro que las reglas actuales con respecto a la mutabilidad tienen algún valor, pero son caras. Son una especie de abstracción fluida: es decir, cuentan una historia simple, que de hecho resulta ser incompleta. Esto lleva a confusión cuando las personas pasan de una comprensión inicial, en la cual &mut refleja cómo funciona la mutabilidad, a una comprensión completa: a veces mut solo mut necesario para garantizar la unicidad, y a veces la mutabilidad se logra sin la palabra clave mut .


Además, debemos actuar con precaución para mantener la ficción, que denota mutabilidad, no singularidad. Hemos agregado casos especiales para que el prestatario verifique los cierres. Debemos hacer que las reglas sobre mutabilidad &mut más complejas en general. Debemos agregar mut a los cierres para poder llamarlos, o hacer que la sintaxis de los cierres se abra de una manera menos obvia. Y así sucesivamente.


Al final, todo se convierte en un lenguaje más complejo en su conjunto. En lugar de solo pensar en la propiedad compartida y la singularidad, el usuario debe pensar en la propiedad compartida y la mutabilidad, y ambos están de alguna manera en mal estado.


No creo que valga la pena.

Source: https://habr.com/ru/post/es418735/


All Articles