Ponteiros inteligentes em Rust
(smart pointers - parte 2)
(Rc<T> e RefCell<T>)
Clube do Livro #15

Rc<T> - contador de referências
- Permite que um único valor tenha vários donos através de referências imutáveis
- Mantém um registro do número de referências a um valor; quando este número se torna zero, a memória é liberada.
- Aplicação: disponibilizar dados na heap para várias partes de nosso programa, quando não podemos determinar em tempo de compilação qual parte terminará de usar os dados por último.
- Limitações: somente single-threaded
Clube do Livro #15
Clube do Livro #15

Estudo de caso: Cons list compartilhada

Clube do Livro #15
Clube do Livro #15
Cons list compartilhada
- Poderíamos ter usado referências
- Vamos usar Rc<T> para clonar a lista a; múltiplos donos
- Cada vez que clonarmos, o contador de referências será incrementado; cada vez que droparmos um, o contador de referência será decremetado; chegando a zero, temos o drop.
- Usamos Rc::clone ao invés de a.clone():
- a.clone() faz uma cópia profunda dos dados; Rc::clone apenas aumenta a contagem da referência.
- diferença visual entre os dois: facilidade na detecção de bugs
- Rc::strong_count() nos dá a contagem de referências
Clube do Livro #15
RefCell<T> e mutabilidade interior
- Relembrando regras de borrowing
- RefCell: verifica as regras em runtime, ao invés de compile time
- Compilador conservador
- Programador tem certeza que o código segue as regras
- Limitação: código single-threaded
- Mutabilidade Interior:
- Design pattern em Rust que permite modificar dados mesmo quando eles são de uma referência imutável
- Utilização de código unsafe dentro de uma estrutura que expõe uma API safa
Comparação entre smart-pointers
Clube do Livro #15
| Box<T> | Rc<T> | RefCell<T> | |
|---|---|---|---|
| Donos | Um | Múltiplos | Um |
| Borrowing | Imutável/Mut | Imutável | Imutável/Mut |
| Verificação | Compilação | Compilação | Execução |
| Mut. Interior | Não | Não | Sim |
Mutabilidade interior
- Um valor imutável não pode ser emprestado como mutável
- Existem situações em que seria útil se um valor mudasse a si mesmo, mas continuasse parecendo imutável externamente
- RefCell nos permite fazer isso
- Não viola as regras de borrowing, que são verificadas em tempo de execução ao invés de compilação
- Ao invés de um erro de compilação, nosso programa termina num panic! se violarmos as regras
- Desvantagens: pegamos erros somente depois, em execução; há pequena degradação na performance
Clube do Livro #15
Estudo de caso: dublês de teste
- Dublê de teste: um tipo usado no lugar de outro durante um teste
- Objeto Mock: grava o que acontece durante um teste para verificarmos se tudo ocorreu como esperado
- Criaremos uma biblioteca que monitora um valor contra determinados limites e envia mensagens baseadas no quão perto o valor está desses limites
- definirá as mensagens
- monitorará os valores
- abstração: usuários da biblioteca devem implementar como as mensagens serão enviadas
Clube do Livro #15
Estudo de caso: dublês de teste
- Implementação da Trait Messenger e da struct LimitTracker
- método send: pega uma referência imutável para self e o texto da mensagem - essa interface que o nosso objeto terá que implementar para fazermos o teste
- método set_value: temos que testar se o mensageiro envia as mensagens corretas de acordo com o valor setado
- Nosso objeto mock, ao invés de enviar as mensagens, vai guardá-las num vetor interno para que possamos checá-las depois
- Nosso teste criará o objeto MockMessenger, implementará a trait Messenger para ele e passará o MockMessenger para um LimitTracker, com o qual manipularemos o valor.
Clube do Livro #15
Estudo de caso: dublês de teste
- Problema: não podemos modificar MockMessenger para adicionar as mensagens, porque o método send recebe uma referência imutável de self.
- Por outro lado, não podemos mudar a assinatura de send, pois isso implicaria na mudança da trait
- A mutabilidade interior vai ajudar neste caso: encapsularemos sent_messages dentro de um RefCell<T>
- borrow_mut nos retorna uma referência mutável do vetor encapsulado por RefCell em sent_messages, isso nos permite adicionar mensagens
- borrow nos retorna uma referência imutável, o que nos permite verificar as mensagens
- As regras de borrowing continuam válidas
Clube do Livro #15
Rc<T> junto com RefCell<T>
- Múltiplos donos de dados mutáveis:
- Rc<T> permite múltipla propriedade
- RefCell permite alterar os dados através da mutabilidade interior.
Clube do Livro #15
Vazamentos de memória
- Vazamentos de memória: memória nunca é liberada para outros processos
- Em Rust é difícil, mas não impossível tê-los
- Ao usar Rc<T> e RefCell<T> é possível ter itens que referenciam a si mesmos
- Isso cria um vazamento porque a contagem de referências nunca vai a zero e os valores nunca são dropados
- Vamos criar um vazamento de memória
Clube do Livro #15
Criando um vazamento
- Vamos utilizar a nossa velha conhecida Cons List
- O segundo valor é agora um RefCell<Rc<List>>, para podermos modificar qual lista estamos apontando
- Criaremos um método tail (calda) que retorna um Option para o valor do segundo item, se definido
- Criaremos uma referência cíclica:
- Criaremos uma lista a e uma lista b, que aponta para a
- Depois alteraremos a para apontar para b
- Nessa situação, já temos um memory leak
- Ao chamar o método tail teremos um stack overflow
Clube do Livro #15

Clube do Livro #15
Lidando com vazamentos
- Um vazamento é um bug em nossa lógica
- Ao lidarmos com valores RefCell<T> que contêm valores Rc<T>, temos que tomar cuidado com o nosso código, pois o compilador não vai detectar vazamentos.
- code reviews
- testes automáticos
- best-praticces
- organização entre referências que expressam propriedade e referências que não expressam, alternando-as
- uso de referências fracas
Clube do Livro #15
Referências fracas
- Até agora somente trabalhamos com referências fortes
- Referências fracas não expressam propriedade; criar uma não aumenta strong_count
-
Rc::downgrade aumenta o número de referências fracas e retorna um smart-pointer Weak<T>, aumenta weak_count
- weak_count não precisa chegar a zero para que possamos liberar o valor
- Ao lidar com Weak<T> você precisa primeiro verificar se o valor existe: método upgrade que retorna Option
Clube do Livro #15
Estudo de caso: árvore
- Criaremos uma árvore em que cada nó terá duas referências: uma para seus filhos e outra para seu pai
- Os filhos são propriedade do pai, e no nosso caso essa propriedade tem que ser compartilhada para permitir acesso direto
- Também precisamos alterar qual nó é filho do outro, por isso usamos RefCell<T>
- Para evitar referências cíclicas, usaremos Weak<T> para definir uma referência ao pai num filho. Um pai deve ser dono de seus filhos, mas os filhos não são donos do pai.
Clube do Livro #15
Estudo de caso: árvore
Clube do Livro #15

Obrigado, pessoal!

@felubra
twitter, github, telegram:
Referências
Clube do Livro #15
Ponteiros inteligentes em Rust (parte 2)
By Felipe Lube de Bragança
Ponteiros inteligentes em Rust (parte 2)
Material de apoio para apresentação do capítulo 15 do Livro do Rust em nosso canal do youtube, dia 24/01/2021 às 21h.
- 28