En tant que nouveau venu à Rust, je crois comprendre que les durées de vie explicites ont deux objectifs.
Mettre une annotation explicite à vie sur une fonction restreint le type de code qui peut apparaître à l'intérieur de cette fonction. Des durées de vie explicites permettent au compilateur de s'assurer que votre programme fait ce que vous vouliez.
Si vous (le compilateur) voulez (s) vérifier si un morceau de code est valide, vous (le compilateur) n'aurez pas à regarder itérativement à l'intérieur de chaque fonction appelée. Il suffit de regarder les annotations des fonctions qui sont directement appelées par ce morceau de code. Cela rend votre programme beaucoup plus facile à raisonner (le compilateur) et rend les temps de compilation gérables.
Au point 1, considérons le programme suivant écrit 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()
qui imprimera
array([[1, 0],
[0, 0]])
Ce type de comportement me surprend toujours. Ce qui se passe, c'est le df
partage de la mémoire avec ar
, donc quand une partie du contenu des df
changements work
, ce changement infecte ar
aussi. Cependant, dans certains cas, cela peut être exactement ce que vous voulez, pour des raisons d'efficacité de la mémoire (pas de copie). Le vrai problème dans ce code est que la fonction second_row
renvoie la première ligne au lieu de la seconde; bonne chance pour déboguer ça.
Considérez plutôt un programme similaire écrit 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);
}
Compiler cela, vous obtenez
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> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
En fait, vous obtenez deux erreurs, il y en a aussi une avec les rôles de 'a
et 'b
échangés. En regardant l'annotation de second_row
, nous constatons que la sortie doit être &mut &'b mut [i32]
, c'est-à-dire que la sortie est censée être une référence à une référence avec une durée de vie 'b
(la durée de vie de la deuxième ligne de Array
). Cependant, comme nous renvoyons la première ligne (qui a une durée de vie 'a
), le compilateur se plaint de la non-concordance de la durée de vie. Au bon endroit. Au bon moment. Le débogage est un jeu d'enfant.