Me deparei com o seguinte cenário:
fn compute_values(value: &mut String) {
// the first computation should always work:
let computed_value = String::from("computed value");
// override the default String with the computed value:
*value = computed_value;
// some more code that returns a Result with values or an error message
}
fn main() {
let mut value = String::from("default value");
compute_values(&mut value);
println!("value: {value}")
}
Este código é compilado com my rustc 1.74.1 (a28077b28 2023-12-04)
e gera value: computed value
como eu esperava, mas a questão é: se esse código vazar memória.
Pelo que entendi, ele *value = computed_value;
entra e não está mais disponível (o compilador Rust confirma isso, não posso depois) e não é desalocado quando o escopo da função termina. Como esta linha está efetivamente atribuindo a a a , suponho que todos os campos da estrutura sejam copiados para o local da estrutura original. Mas uma vez que a referência contida original é substituída, não há como desalocar a memória heap original que o antigo apontava. Como não estou usando nenhum Rust inseguro e o Rust parece ser seguro para a memória, só posso imaginar que a memória original seja desalocada automaticamente antes que a atribuição aconteça, mas não consegui encontrar nenhuma informação sobre esse tópico.computed_value
*value
println!("{computed_value}");
struct String
struct String
&str
&str
Encontrei os seguintes artigos que não responderam à minha pergunta:
- Existe uma maneira de atualizar uma string enferrujada?
- Como posso trocar um novo valor por um campo em uma referência mutável a uma estrutura?
- Como posso substituir todos os campos em uma referência mutável usando outra estrutura?
As respostas nomeadas std::mem::take
e std::mem::swap
que std::mem::replace
me levam a crer que o uso de uma delas é necessário neste caso.
Também verifiquei a documentação oficial sobre ferrugem, mas não encontrei nada.
Minhas perguntas são:
- Este código está vazando memória?
- O Rust não inseguro deveria ser seguro para a memória, mesmo ao substituir valores de referências mutáveis?
- Existe alguma documentação (oficial) sobre a atribuição de desreferência?
PS: Teoricamente, o Rust pode otimizar let mut value = String::from("default value");
de forma que String
value
aponte para a constante da string e não para um local no heap. Isso não tornaria necessário desalocar a memória. Mas a coisa toda também funciona quando você lê a value
from stdin e a coisa toda deve ser necessariamente colocada na pilha.
Responder
Depois de ler a resposta de @cyqsimon (que é ótima, aliás), entendi que essa pergunta realmente não tem nada a ver com referências e, em vez disso, trata de descartar valores quando eles são substituídos. Armado com esse novo insight, encontrei este artigo do Rustonomicon que responde totalmente à minha pergunta e também fala sobre o que acontece quando as variáveis são apenas inicializadas e, portanto, descartadas sob certas condições.
Não não é. A
String
estrutura antiga não pertence mais a nada após a atribuição, portanto ela é descartada automaticamente (e, portanto, devidamente limpa).A segurança da memória do Rust garante muitas coisas - não vazar memória não é uma delas. Na verdade, existe explicitamente uma
Box::leak
função que faz exatamente o que você pensa que faz. Neste caso específico, suponho que poderia haver vazamento de memória com umaDrop
característica implementada incorretamente, embora geralmente isso não seja algo com que você deva se preocupar.Esse comportamento de "eliminar o valor antigo" faz parte da operação de atribuição, e não da operação de desreferência. Está documentado aqui . (Obrigado @user2722968)
A partir de
std::mem::{swap, take, replace}
, essas são abstrações seguras que visam satisfazer a estipulação segura do Rust de que todos os valores devem ser válidos em todos os momentos. Sem eles, se você quiser mover um valor para fora de uma referência mutável, semanticamente haveria um instante em que a ligação da variável seria indefinida, o que novamente não é permitido no Rust seguro. Se você der uma olhada no código fonte dessas funções (elas são bastante simples), tudo o que elas estão fazendo é memcopy.Nesse caso, se você quiser manter
String::from("default value")
após a reatribuição em vez de descartar esse valor, você usariastd::mem::replace
.Não é relevante para esta questão, mas não acredito que a otimização de pequenas strings seja implementada no Rust (e na verdade pode não ser possível implementar no Rust). Lembro-me de que Jack O'Connor falou brevemente sobre por que isso acontece em seu excelente vídeo .