Come nuovo arrivato in Rust, la mia comprensione è che le vite esplicite hanno due scopi.
Inserendo un'annotazione di durata esplicita su una funzione si limita il tipo di codice che può apparire all'interno di quella funzione. Le vite esplicite consentono al compilatore di assicurarsi che il programma stia eseguendo ciò che intendevi.
Se tu (il compilatore) vuoi verificare se un pezzo di codice è valido, tu (il compilatore) non dovrai guardare iterativamente all'interno di ogni funzione chiamata. È sufficiente dare un'occhiata alle annotazioni delle funzioni chiamate direttamente da quel pezzo di codice. Questo rende il tuo programma molto più facile da ragionare per te (il compilatore) e rende gestibili i tempi di compilazione.
Al punto 1., considera il seguente programma scritto in 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()
che stamperà
array([[1, 0],
[0, 0]])
Questo tipo di comportamento mi sorprende sempre. Quello che sta succedendo è che df
sta condividendo la memoria ar
, quindi quando parte del contenuto dei df
cambiamenti work
, anche quel cambiamento infetta ar
. Tuttavia, in alcuni casi questo può essere esattamente quello che vuoi, per motivi di efficienza della memoria (nessuna copia). Il vero problema in questo codice è che la funzionesecond_row
sta restituendo la prima riga invece della seconda; buona fortuna debug.
Considera invece un programma simile scritto in 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 questo, ottieni
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> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
In effetti si ottengono due errori, ce n'è anche uno con i ruoli di 'a
e 'b
scambiati. Osservando l'annotazione di second_row
, troviamo che l'output dovrebbe essere &mut &'b mut [i32]
, ovvero, l'output dovrebbe essere un riferimento a un riferimento con la durata 'b
(la durata della seconda riga di Array
). Tuttavia, poiché stiamo restituendo la prima riga (che ha una durata 'a
), il compilatore si lamenta della mancata corrispondenza della durata. Nel posto giusto Al momento giusto. Il debugging è un gioco da ragazzi.