rust - ¿Qué son las vidas no léxicas?
lifetime lifetime-scoping (1)
Es más fácil entender qué son las vidas no léxicas al entender qué son las vidas lexical . En las versiones de Rust antes de que existan tiempos de vida no léxicos, 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 las
scores
se toman prestadas por la variable de
score
, por lo que no permite una mutación adicional de las
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 ser humano puede ver trivialmente que este ejemplo es demasiado conservador: ¡la
score
nunca se usa
!
El problema es que el préstamo de
scores
por
score
es
lexical
: 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 vidas útiles no léxicas lo solucionan mejorando el compilador para comprender este nivel de detalle. El compilador ahora puede decir con mayor precisión cuándo se necesita un préstamo y este código se compilará.
Algo maravilloso de las vidas no léxicas es que una vez habilitado, nadie pensará en ellas . Simplemente se convertirá en "lo que hace Rust" y las cosas (con suerte) simplemente funcionarán.
¿Por qué se permitieron las vidas léxicas?
Rust está destinado a permitir que solo se compilen los programas seguros conocidos. Sin embargo, es imposible permitir exactamente solo programas seguros y rechazar programas inseguros. Con ese fin, Rust se equivoca al ser conservador: algunos programas seguros son rechazados. Las vidas léxicas son un ejemplo de esto.
Las vidas léxicas eran 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. El compilador necesitaba ser reescrito para introducir y hacer uso de una "representación intermedia de nivel medio" (MIR) . Luego, el verificador de préstamos (aka "borrowck") tuvo que ser reescrito para usar MIR en lugar del árbol de sintaxis abstracta (AST). Luego, las reglas del verificador de préstamos tuvieron que ser refinadas para ser más detalladas.
Las vidas léxicas no siempre se interponen en el camino del programador, y hay muchas maneras de solucionar las vidas léxicas cuando lo hacen, incluso si son molestas. En muchos casos, esto implicaba agregar llaves extra o un valor booleano. Esto permitió que Rust 1.0 se enviara y fuera útil durante muchos años antes de que se implementaran tiempos de vida no léxicos.
Curiosamente, ciertos
buenos
patrones se desarrollaron debido a tiempos de vida léxicos.
El primer ejemplo para mí es
el patrón de
entry
.
Este código falla antes de los tiempos de vida no léxicos 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 las vidas léxicas 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 intervalo 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 tiempos de vida no léxicos no cambia los tiempos de vida de ningún valor, por lo que no puede hacer que los tiempos de vida no sean léxicos. Solo 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 pretendieron ser una característica "orientada al usuario", per se . En su mayoría, han crecido mucho en nuestras mentes debido a los pequeños papercuts que recibimos de su ausencia. Su nombre estaba destinado principalmente a propósitos de desarrollo interno y cambiarlo por propósitos de marketing nunca fue una prioridad.
Sí, pero ¿cómo lo uso?
En Rust 1.31 (publicado el 2018-12-06), debe suscribirse a la edición de 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 de Rust 2015 también permite tiempos de vida no léxicos.
La implementación actual de tiempos de vida no léxicos está en un "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 que le informa que es probable que su código se rompa en una versión futura de Rust y que debe actualizarse.
En las versiones nocturnas de Rust, puede optar por la rotura forzada a través de 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 por tiempos de vida no léxicos.
- ¿Devolver una referencia de un HashMap o Vec hace que un préstamo dure más allá del alcance en el que está?
- ¿Por qué HashMap :: get_mut () toma posesión del mapa para el resto del alcance?
- No se puede pedir prestado como inmutable porque también se toma como mutable en los argumentos de la función
- ¿Cómo actualizar o insertar en un Vec?
- ¿Hay alguna forma de liberar un enlace antes de que quede fuera 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
- Al devolver el resultado de consumir un StdinLock, ¿por qué se retuvo el préstamo a stdin?
- Error movido colateralmente al deconstruir una caja de pares
Rust tiene un RFC relacionado con tiempos de vida no léxicos que ha sido aprobado para ser implementado en el idioma durante mucho tiempo. Recently , el soporte de Rust de esta función ha mejorado mucho y se considera completo.
Mi pregunta es: ¿qué es exactamente una vida no léxica?