O que Rust tem em vez de um coletor de lixo?

93

Eu entendo que Rust não tem um coletor de lixo e estou me perguntando como a memória é liberada quando uma ligação sai do escopo.

Portanto, neste exemplo, entendo que Rust recupera a memória alocada para 'a' quando ele sai do escopo.

{
    let a = 4
}

O problema que estou tendo com isso, em primeiro lugar, é como isso acontece e, em segundo lugar, isso não é uma espécie de coleta de lixo? Em que difere da coleta de lixo 'típica'?

Rix
fonte
12
"Vida útil de objeto determinística". Semelhante ao C ++.
user2864740
@ user2864740 Esse guia está bem desatualizado. A substituição moderna provavelmente seria doc.rust-lang.org/book/references-and-borrowing.html .
Veedrac de

Respostas:

73

A coleta de lixo é normalmente usada periodicamente ou sob demanda, como se o heap estiver quase cheio ou acima de algum limite. Em seguida, ele procura variáveis ​​não utilizadas e libera sua memória, dependendo do algoritmo .

Rust saberia quando a variável sai do escopo ou seu tempo de vida termina no tempo de compilação e, portanto, inserir as instruções LLVM / assembly correspondentes para liberar a memória.

Rust também permite algum tipo de coleta de lixo, como a contagem de referência atômica .

Ayonix
fonte
Alocando memória ao introduzir variáveis ​​e liberando memória quando ela não é mais necessária? Eu realmente não sei o que você quer dizer com isso. Talvez tenhamos opiniões diferentes sobre o que é um GC então.
Ayonix de
1
Sua pergunta é como a abordagem de Rust difere de um GC típico. Então, expliquei o que é um GC e como Rust faz isso sem um GC.
Ayonix de
1
doc.rust-lang.org/book/the-stack-and-the-heap.html explica muito bem. Sim, muitas coisas estão na pilha, mas muito menos não há um indicador suficiente (consulte o Quadro). Eu deixei isso de fora por uma questão de simplicidade, já que a pergunta era geralmente feita
Ayonix
1
@Amomum Na verdade, Rust não tem nenhuma new()função ungida como C, elas são apenas funções estáticas e, em particular, algo como let x = MyStruct::new()cria seu objeto na pilha. O indicador real de alocação de heap é Box::new()(ou qualquer uma das estruturas que dependem do Box).
Mario Carneiro
1
Quais outras linguagens tratam do gerenciamento de memória de maneira semelhante ao Rust?
still_dreaming_1
42

A ideia básica de gerenciar recursos (incluindo memória) em um programa, seja qual for a estratégia, é que os recursos vinculados a "objetos" inacessíveis podem ser recuperados. Além da memória, esses recursos podem ser bloqueios mutex, identificadores de arquivo, sockets, conexões de banco de dados ...

As linguagens com um coletor de lixo varrem periodicamente a memória (de uma forma ou de outra) para localizar objetos não usados, liberar os recursos associados a eles e, finalmente, liberar a memória usada por esses objetos.

Rust não tem GC, como funciona?

A ferrugem tem propriedade. Usando um sistema de tipo afim , ele rastreia qual variável ainda está segurando um objeto e, quando tal variável sai do escopo, chama seu destruidor. Você pode ver o sistema de tipo afim em vigor com bastante facilidade:

fn main() {
    let s: String = "Hello, World!".into();
    let t = s;
    println!("{}", s);
}

Rendimentos:

<anon>:4:24: 4:25 error: use of moved value: `s` [E0382]
<anon>:4         println!("{}", s);

<anon>:3:13: 3:14 note: `s` moved here because it has type `collections::string::String`, which is moved by default
<anon>:3         let t = s;
                     ^

o que ilustra perfeitamente que em qualquer momento, no nível da linguagem, a propriedade é rastreada.

Esta propriedade funciona de forma recursiva: se você tem um Vec<String>(ou seja, uma matriz dinâmica de cordas), então cada um Stringé de propriedade do Vecque em si é propriedade de uma variável ou um outro objeto, etc ... Assim, quando uma variável sai do escopo, ele libera recursivamente todos os recursos que mantinha, mesmo indiretamente. No caso de Vec<String>isso significa:

  1. Liberando o buffer de memória associado a cada String
  2. Liberando o buffer de memória associado ao Vecpróprio

Assim, graças ao rastreamento de propriedade, o tempo de vida de TODOS os objetos do programa está estritamente ligado a uma (ou várias) variáveis ​​de função, que acabarão por sair do escopo (quando o bloco ao qual eles pertencem terminar).

Nota: isso é um pouco otimista, usando a contagem de referências ( Rcou Arc) é possível formar ciclos de referências e assim causar vazamentos de memória, caso em que os recursos vinculados ao ciclo podem nunca ser liberados.

Matthieu M.
fonte
2
"Idiomas com coletor de lixo varrem periodicamente a memória (de uma forma ou de outra)". Muitos fazem, mas isso não é verdade em geral. Os coletores de lixo em tempo real fazem a varredura de forma incremental em vez de periodicamente. Linguagens de contagem de referência, como Mathematica, não fazem a varredura.
JD
@JonHarrop: Não considero a contagem de referência um mecanismo completo de Coleta de Lixo, pois deve ser complementado para evitar ciclos de vazamento. Quanto à diferença incremental / periódica, pode ser meu péssimo domínio do inglês, mas não consigo ver como periódico não cobre o caso incremental ... Acho que o bit "(de uma forma ou de outra)" transmite adequadamente tantas existem abordagens. Em qualquer caso, se você tiver uma maneira melhor de descrever sucintamente a coleta de lixo, por favor, sugira. Não tenho, no entanto, nenhuma intenção de me lançar em uma explicação completa: não estou qualificado para isso.
Matthieu M.
1
“Não considero a contagem de referência um mecanismo completo de coleta de lixo, pois deve ser complementado para evitar ciclos de vazamento”. RC é convencionalmente considerado uma forma de GC. No Mathematica e Erlang, por exemplo, os ciclos não podem ser criados por design, então o RC não vaza. Para uma perspectiva de alto nível, consulte "Uma teoria unificada da coleta de lixo" cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
JD
@JonHarrop: Verdadeiro, se nenhum ciclo for possível, o RC não poderá vazar.
Matthieu M.
2
“Não consigo ver como o periódico não cobre o caso incremental”. Os algoritmos de parada do mundo seriam considerados periódicos, enquanto a marcação tricolor é considerada incremental, por exemplo. Eles são opostos neste contexto.
JD de
6

Com uma linguagem em que você deve gerenciar manualmente a memória, a distinção entre a pilha e o heap torna-se crítica. Cada vez que você chama uma função, espaço suficiente é alocado na pilha para todas as variáveis ​​contidas no escopo dessa função. Quando a função retorna, o quadro de pilha associado a essa função é "retirado" da pilha e a memória é liberada para uso futuro.

Do ponto de vista prático, esta limpeza de memória inadvertida é usada como um meio de armazenamento automático de memória que será apagado no final do escopo da função.

Há mais informações disponíveis aqui: https://doc.rust-lang.org/book/the-stack-and-the-heap.html

suíço
fonte
3
Embora usar a pilha seja útil, a vida útil do objeto determinístico ainda pode ser tratada se todos os valores forem 'criados na pilha'. Portanto, é um detalhe de implementação; não necessariamente uma estratégia de linguagem.
user2864740
2
Você continua usando essa palavra. Eu não acho que significa o que você pensa que significa.
Suíça de
Significa o que desejo expressar ; sendo o oposto de vidas não determinísticas. Faça uma oferta para uma frase melhor.
user2864740 de
Obrigado pela resposta, dei os pontos para o primeiro simplesmente porque foi enviado primeiro. A informação é igualmente útil e válida.
rix de
@ user2864740 Vida útil de objeto determinística refere-se a ser capaz de dizer exatamente quando a memória do objeto será apagada assim que seu destruidor for chamado. Não tem nada a ver com como esse destruidor é chamado em primeiro lugar. Você continua mencionando o mesmo termo repetidamente, embora ele não tenha nenhum significado direto para a pergunta.
Suíça de