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

Made with Slides.com