Como recién llegado a Rust, entiendo que las vidas explícitas tienen dos propósitos.
Poner una anotación explícita de por vida en una función restringe el tipo de código que puede aparecer dentro de esa función. Las vidas explícitas permiten que el compilador se asegure de que su programa esté haciendo lo que usted pretendía.
Si usted (el compilador) quiere comprobar si un código es válido, usted (el compilador) no tendrá que buscar de forma iterativa dentro de cada función llamada. Basta con echar un vistazo a las anotaciones de funciones que ese código llama directamente. Esto hace que su programa sea mucho más fácil de razonar para usted (el compilador) y hace que los tiempos de compilación sean manejables.
En el punto 1., considere el siguiente programa escrito en Python:
import pandas as pd
import numpy as np
def second_row(ar):
return ar[0]
def work(second):
df = pd.DataFrame(data=second)
df.loc[0, 0] = 1
def main():
# .. load data ..
ar = np.array([[0, 0], [0, 0]])
# .. do some work on second row ..
second = second_row(ar)
work(second)
# .. much later ..
print(repr(ar))
if __name__=="__main__":
main()
que imprimirá
array([[1, 0],
[0, 0]])
Este tipo de comportamiento siempre me sorprende. Lo que está sucediendo es que df
está compartiendo memoria ar
, por lo que cuando parte del contenido de los df
cambios en work
ese cambio también infecta ar
. Sin embargo, en algunos casos esto puede ser exactamente lo que desea, por razones de eficiencia de memoria (sin copia). El verdadero problema en este código es que la funciónsecond_row
está devolviendo la primera fila en lugar de la segunda; buena suerte depurando eso.
Considere en su lugar un programa similar escrito en Rust:
#[derive(Debug)]
struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);
impl<'a, 'b> Array<'a, 'b> {
fn second_row(&mut self) -> &mut &'b mut [i32] {
&mut self.0
}
}
fn work(second: &mut [i32]) {
second[0] = 1;
}
fn main() {
// .. load data ..
let ar1 = &mut [0, 0][..];
let ar2 = &mut [0, 0][..];
let mut ar = Array(ar1, ar2);
// .. do some work on second row ..
{
let second = ar.second_row();
work(second);
}
// .. much later ..
println!("{:?}", ar);
}
Compilando esto, obtienes
error[E0308]: mismatched types
--> src/main.rs:6:13
|
6 | &mut self.0
| ^^^^^^^^^^^ lifetime mismatch
|
= note: expected type `&mut &'b mut [i32]`
found type `&mut &'a mut [i32]`
note: the lifetime 'b as defined on the impl at 4:5...
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
--> src/main.rs:4:5
|
4 | impl<'a, 'b> Array<'a, 'b> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
De hecho, obtienes dos errores, también hay uno con los roles de 'a
e 'b
intercambiado. Al second_row
observar la anotación de , encontramos que la salida debería ser &mut &'b mut [i32]
, es decir, se supone que la salida es una referencia a una referencia con duración 'b
(la duración de la segunda fila de Array
). Sin embargo, debido a que estamos devolviendo la primera fila (que tiene una vida útil 'a
), el compilador se queja de una falta de coincidencia de por vida. En el lugar correcto En el momento adecuado. La depuración es muy fácil.