Rc<T>
Rc<T> é um tipo de Smart Pointer
com contagem de referências, o seu nome vem de Reference Counting
, parecido com o Box<T>, porém com contagem de referências e sem a implementação da trait DerefMut
, sendo assim é um tipo de Smart Pointer
imutável. Sua declaração é parecida com a de um Box<T>, mas o que vai diferenciá-lo?
Vamos supor que temos o seguinte código:
fn main() { let numero = Box::new(69); escreva(numero); faca_qualquer_coisa(numero); } fn faca_qualquer_coisa<T>(valor: Box<T>) { todo!() } fn escreva<T: std::fmt::Display>(valor: Box<T>) { println!("valor = {}", valor) }
O código acima ira nos retornar o seguinte erro:
error[E0382]: use of moved value: `numero`
--> ref-count.rs:4:25
|
2 | let numero = Box::new(69);
| ------ move occurs because `numero` has type `Box<i32>`, which does not implement the `Copy` trait
3 | escreva(numero);
| ------ value moved here
4 | faca_qualquer_coisa(numero);
| ^^^^^^ value used here after move
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0382`.
Isso acontece justamente pelas regras de ownership do Rust. Um modo para contornarmos esse problema seria utilizando o Rc<T>, e aqui que ele tem uma vantagem. Por ser uma referência compartilhada podemos clonar esta referência, claro apenas as informações necessárias, como o endereço de memória e... Só.
use std::rc::Rc; fn main() { let numero = Rc::new(69); escreva(Rc::clone(&numero)); faca_qualquer_coisa(Rc::clone(&numero)); } fn faca_qualquer_coisa<T>(valor: Rc<T>) { todo!() } fn escreva<T: std::fmt::Display>(valor: Rc<T>) { println!("valor = {}", valor) }
Realizamos o clone pelo chamando a implementação da struct
Rc<T>, mas nada nos impede de chamar o método .clone()
da instância, porém não é o padrão utilizado por ai.
Este tipo de ponteiro, somente irá liberar a memória alocada quando o seu contador de referências chegar a 0. Ou seja, ninguém mais, apontar para aquela região de memória. No exemplo abaixo, podemos ver como saber o atual valor deste contador.
use std::rc::Rc; fn main() { let referência = Rc::new(42); println!("contador atual esta em: {}", Rc::strong_count(&referência)); let segunda_referência = Rc::clone(&referência); println!("contador atual esta em: {}", Rc::strong_count(&referência)); { let terceira_referência = Rc::clone(&segunda_referência); println!("contador atual esta em: {}", Rc::strong_count(&referência)); } println!("contador atual esta em: {}", Rc::strong_count(&referência)); } /* após o fim do escopo da função main, o contador chega a '0' a memória é liberada. */
A saída do programa acima é:
contador atual esta em: 1
contador atual esta em: 2
contador atual esta em: 3
contador atual esta em: 2
Repare que o valor do contador muda, independente de realizar o clone
a partir da primeira referência, ou da segunda referência, isso acontece, porque os dois apontam para o mesmo local da memória.