Como recém-chegado ao Rust, meu entendimento é que vidas explícitas servem a dois propósitos.
Colocar uma anotação de duração explícita em uma função restringe o tipo de código que pode aparecer dentro dessa função. A vida útil explícita permite ao compilador garantir que seu programa esteja fazendo o que você pretendia.
Se você (o compilador) deseja verificar se um trecho de código é válido, você (o compilador) não precisará procurar iterativamente todas as funções chamadas. Basta dar uma olhada nas anotações de funções chamadas diretamente por esse trecho de código. Isso torna seu programa muito mais fácil de raciocinar para você (o compilador) e torna os tempos de compilação gerenciáveis.
No ponto 1., considere o seguinte programa escrito em 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]])
Esse tipo de comportamento sempre me surpreende. O que está acontecendo é que o df
compartilhamento de memória é feito ar
; portanto, quando parte do conteúdo das df
alterações é work
afetada ar
também. No entanto, em alguns casos, isso pode ser exatamente o que você deseja, por motivos de eficiência de memória (sem cópia). O verdadeiro problema nesse código é que a função second_row
está retornando a primeira linha em vez da segunda; boa sorte depurando isso.
Considere um programa semelhante escrito em 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 isso, você obtém
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> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
Na verdade, você recebe dois erros, há também um com os papéis de 'a
e 'b
intercambiado. Observando a anotação de second_row
, descobrimos que a saída deve ser &mut &'b mut [i32]
, ou seja, a saída deve ser uma referência a uma referência com vida útil 'b
(a vida útil da segunda linha de Array
). No entanto, como estamos retornando a primeira linha (que possui vida útil 'a
), o compilador reclama da incompatibilidade da vida útil. No lugar certo. No tempo certo. Depurar é fácil.