¿Qué son las vidas no léxicas?

Resuelto Stargateur asked hace 6 años • 0 respuestas

Rust tiene un RFC relacionado con vidas no léxicas que ha sido aprobado para su implementación en el lenguaje durante mucho tiempo. Recientemente , el soporte de Rust para esta característica ha mejorado mucho y se considera completo.

Mi pregunta es: ¿ qué es exactamente una vida no léxica?

Stargateur avatar May 09 '18 17:05 Stargateur
Aceptado

Es más fácil entender qué son las vidas no léxicas si comprendes qué son las vidas léxicas . En versiones de Rust anteriores a la existencia de vidas no léxicas, este código fallará:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.push(4);
}

El compilador de Rust ve que scoresla variable está tomada prestada score, por lo que no permite una mayor mutación de scores:

error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let score = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(4);
  |     ^^^^^^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

Sin embargo, un humano puede ver trivialmente que este ejemplo es demasiado conservador: ¡ scorenunca se utiliza ! El problema es que el préstamo de scoresby scorees léxico : dura hasta el final del bloque en el que está contenido:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.push(4);                 //
                                    // <-- score stops borrowing here
}

Las duraciones no léxicas solucionan este problema mejorando el compilador para que comprenda este nivel de detalle. El compilador ahora puede indicar con mayor precisión cuándo se necesita un préstamo y este código se compilará.

Lo maravilloso de las vidas no léxicas es que, una vez habilitadas, nadie pensará jamás en ellas . Simplemente se convertirá en "lo que hace Rust" y las cosas (con suerte) simplemente funcionarán.

¿Por qué se permitieron vidas léxicas?

Rust está destinado a permitir que solo se compilen programas seguros. Sin embargo, es imposible permitir exactamente sólo programas seguros y rechazar los no seguros. Con ese fin, Rust peca de conservador: algunos programas seguros son rechazados. Las vidas léxicas son un ejemplo de esto.

Las duraciones léxicas fueron mucho más fáciles de implementar en el compilador porque el conocimiento de los bloques es "trivial", mientras que el conocimiento del flujo de datos lo es menos. Era necesario reescribir el compilador para introducir y utilizar una "representación intermedia de nivel medio" (MIR) . Luego, el verificador de préstamos (también conocido como "borrowck") tuvo que reescribirse para usar MIR en lugar del árbol de sintaxis abstracta (AST). Luego las reglas del verificador de préstamos tuvieron que ser refinadas para que fueran más detalladas.

Las vidas léxicas no siempre interfieren con el programador, y hay muchas maneras de evitar las vidas léxicas cuando lo hacen, incluso si son molestas. En muchos casos, esto implicó agregar llaves adicionales o un valor booleano. Esto permitió que Rust 1.0 se distribuyera y fuera útil durante muchos años antes de que se implementaran vidas no léxicas.

Curiosamente, se desarrollaron ciertos buenos patrones debido a la duración del léxico. El mejor ejemplo para mí es el entrypatrón . Este código falla antes de la vida útil no léxica y se compila con él:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    match map.get_mut(&key) {
        Some(value) => *value += 1,
        None => {
            map.insert(key, 1);
        }
    }
}

Sin embargo, este código es ineficiente porque calcula el hash de la clave dos veces. La solución que se creó debido a la duración del léxico es más corta y más eficiente:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}

El nombre "vidas no léxicas" no me suena bien

La vida útil de un valor es el lapso de tiempo durante el cual el valor permanece en una dirección de memoria específica (consulte ¿ Por qué no puedo almacenar un valor y una referencia a ese valor en la misma estructura? para obtener una explicación más detallada). La característica conocida como duración no léxica no cambia la duración de ningún valor, por lo que no puede hacer que la duración sea no léxica. Sólo hace que el seguimiento y la verificación de los préstamos de esos valores sean más precisos.

Un nombre más preciso para la función podría ser " préstamos no léxicos ". Algunos desarrolladores de compiladores se refieren al "préstamo basado en MIR" subyacente.

Las vidas no léxicas nunca tuvieron la intención de ser una característica "de cara al usuario", per se . En su mayoría, han crecido en nuestras mentes debido a los pequeños cortes de papel que sufrimos por su ausencia. Su nombre estaba destinado principalmente a fines de desarrollo interno y cambiarlo con fines de marketing nunca fue una prioridad.

Sí, pero ¿cómo lo uso?

En Rust 1.31 (lanzado el 6 de diciembre de 2018), debe suscribirse a la edición Rust 2018 en su Cargo.toml:

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]
edition = "2018"

A partir de Rust 1.36, la edición Rust 2015 también permite vidas no léxicas.

La implementación actual de vidas no léxicas se encuentra en "modo de migración". Si el verificador de préstamos NLL pasa, la compilación continúa. Si no es así, se invoca el verificador de préstamos anterior. Si el antiguo verificador de préstamos permite el código, se imprime una advertencia informándole que es probable que su código se rompa en una versión futura de Rust y debe actualizarse.

En las versiones nocturnas de Rust, puedes optar por la rotura forzada mediante un indicador de función:

#![feature(nll)]

Incluso puede optar por la versión experimental de NLL utilizando el indicador del compilador -Z polonius.

Una muestra de problemas reales resueltos mediante vidas no léxicas.

  • ¿Devolver una referencia de un HashMap o Vec hace que un préstamo dure más allá del alcance en el que se encuentra?
  • ¿Por qué HashMap::get_mut() toma posesión del mapa para el resto del alcance?
  • No se puede tomar prestado como inmutable porque también se toma prestado como mutable en los argumentos de la función
  • ¿Cómo actualizar o insertar en un Vec?
  • ¿Hay alguna manera de liberar un enlace antes de que salga del alcance?
  • No se puede obtener una referencia mutable al iterar una estructura recursiva: no se puede pedir prestado como mutable más de una vez a la vez
  • Al devolver el resultado de consumir un StdinLock, ¿por qué se retuvo el préstamo a la entrada estándar?
  • Error de movimiento colateral al deconstruir una Caja de pares
Shepmaster avatar May 09 '2018 12:05 Shepmaster