Mutabilidade Interior

Em algumas situações, é necessário modificar dados atrás de uma referência compartilhada (somente leitura). Por exemplo, uma estrutura de dados compartilhada pode ter um cache interno e desejar atualizar esse cache a partir de métodos somente leitura.

O padrão de “mutabilidade interna” permite acesso exclusivo (mutável) por trás de uma referência compartilhada. A biblioteca padrão fornece várias maneiras de fazer isso, garantindo segurança, geralmente realizando uma verificação em tempo de execução.

RefCell

use std::cell::RefCell;

fn main() {
    // Observe que `cell` NÃO é declarado como mutável.
    let cell = RefCell::new(5);

    {
        let mut cell_ref = cell.borrow_mut();
        *cell_ref = 123;

        // Isso gera um erro em tempo de execução.
        // let other = cell.borrow();
        // println!("{}", *other);
    }

    println!("{cell:?}");
}

Cell

Cell envolve um valor e permite obter ou definir o valor, mesmo com uma referência compartilhada para o Cell. No entanto, não permite nenhuma referência ao valor. Como não há referências, as regras de empréstimo não podem ser quebradas.

use std::cell::Cell;

fn main() {
    // Observe que `cell` NÃO é declarado como mutável.
    let cell = Cell::new(5);

    cell.set(123);
    println!("{}", cell.get());
}
This slide should take about 10 minutes.

O principal a ser observado neste slide é que o Rust fornece maneiras seguras de modificar dados por trás de uma referência compartilhada. Há uma variedade de maneiras de garantir essa segurança, e RefCell e Cell são duas delas.

  • RefCell faz cumprir as regras de empréstimo usuais do Rust (ou várias referências compartilhadas ou uma única referência exclusiva) com uma verificação em tempo de execução. Neste caso, todos os empréstimos são muito curtos e nunca se sobrepõem, então as verificações sempre têm sucesso.

    • O bloco extra no exemplo de RefCell é para encerrar o empréstimo criado pela chamada a borrow_mut antes de imprimir o cell. Tentar imprimir um RefCell emprestado mostra apenas a mensagem "{borrowed}".
  • Cell é um meio mais simples de garantir a segurança: ele tem um método set que recebe &self. Isso não precisa de uma verificação em tempo de execução, mas requer mover valores, o que pode ter seu próprio custo.

  • Tanto RefCell quanto Cell são !Sync, o que significa que &RefCell e &Cell não podem ser passados entre threads. Isso impede que duas threads tentem acessar o cell ao mesmo tempo.