Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories
 Corso Gratuito di Programmazione Rust Lezione 058 – Rc, il puntatore intelligente contato dai riferimenti

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Corso su Rc<T>, il Puntatore Intelligente a Conteggio di Riferimenti

Nella maggior parte dei casi, la proprietà è chiara: sai esattamente quale variabile possiede un determinato valore. Tuttavia, ci sono casi in cui un singolo valore potrebbe avere più proprietari. Ad esempio, nelle strutture dati a grafo, più archi potrebbero puntare allo stesso nodo, e quel nodo è concettualmente posseduto da tutti gli archi che puntano ad esso. Un nodo non dovrebbe essere pulito a meno che non abbia archi che puntano ad esso e quindi non ha proprietari.

Devi abilitare esplicitamente la proprietà multipla utilizzando il tipo Rust Rc<T>, che è un’abbreviazione per conteggio dei riferimenti. Il tipo Rc<T> tiene traccia del numero di riferimenti a un valore per determinare se il valore è ancora in uso. Se non ci sono riferimenti a un valore, il valore può essere pulito senza che nessun riferimento diventi non valido.

Immagina Rc<T> come una TV in una stanza familiare. Quando una persona entra per guardare la TV, la accende. Altre persone possono entrare nella stanza e guardare la TV. Quando l’ultima persona lascia la stanza, spegne la TV perché non viene più utilizzata. Se qualcuno spegne la TV mentre altri la stanno ancora guardando, ci sarebbe un tumulto tra gli spettatori rimanenti!

Usiamo il tipo Rc<T> quando vogliamo allocare alcuni dati nell’heap per più parti del nostro programma per leggere e non possiamo determinare a tempo di compilazione quale parte finirà per utilizzare i dati per ultima. Se sapessimo quale parte finirà per ultima, potremmo semplicemente fare di quella parte il proprietario dei dati, e le normali regole di proprietà applicate a tempo di compilazione entrerebbero in vigore.

Nota che Rc<T> è utilizzato solo in scenari single-threaded. Quando discutiamo di concorrenza nel Capitolo 16, parleremo di come fare il conteggio dei riferimenti in programmi multithreaded.

Utilizzare Rc<T> per Condividere Dati

Torniamo al nostro esempio di lista cons nel Listato 15-5. Ricordiamo che l’abbiamo definita utilizzando Box<T>. Questa volta, creeremo due liste che condividono entrambe la proprietà di una terza lista. Concettualmente, questo assomiglia a Figure 15-3: Due liste che condividono la proprietà di una terza lista

Creeremo una lista a che contiene 5 e poi 10. Poi faremo altre due liste: b che inizia con 3 e c che inizia con 4. Entrambe le liste b e c continueranno poi alla prima lista a contenente 5 e 10. In altre parole, entrambe le liste condivideranno la prima lista contenente 5 e 10.

Provare a implementare questo scenario utilizzando la nostra definizione di List con Box<T> non funzionerà, come mostrato nel Listato 15-17.

Quando compiliamo questo codice, otteniamo questo errore:

less

$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
error[E0382]: uso di un valore spostato: `a`
--> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - lo spostamento avviene perché `a` ha il tipo `List`, che non implementa il **trait** `Copy`
10 | let b = Cons(3, Box::new(a));
| - valore spostato qui
11 | let c = Cons(4, Box::new(a));
| ^ valore usato qui dopo lo spostamento

Per maggiori informazioni su questo errore, prova rustc --explain E0382. errore: impossibile compilare cons-list a causa di un errore precedente

I varianti Cons possiedono i dati che contengono, quindi quando creiamo la lista b, a viene spostato in b e b possiede a. Quindi, quando proviamo a usare di nuovo a quando creiamo c, non ci è permesso perché a è stato spostato.

Potremmo cambiare la definizione di Cons per contenere riferimenti invece, ma dovremmo specificare i parametri di lifetime. Specificando i parametri di lifetime, stiamo specificando che ogni elemento nella lista vivrà almeno quanto l’intera lista. Questo è il caso per gli elementi e le liste nel Listato 15-17, ma non in ogni scenario.

Invece, cambieremo la nostra definizione di List per utilizzare Rc<T> al posto di Box<T>, come mostrato nel Listato 15-18. Ogni variante Cons conterrà ora un valore e un Rc<T> che punta a una List. Quando creiamo b, anziché prendere il possesso di a, cloneremo l’Rc<List> che a sta tenendo, aumentando così il numero di riferimenti da uno a due e permettendo ad a e b di condividere la proprietà dei dati in quell’Rc<List>. Cloneremo anche a quando creiamo c, aumentando il numero di riferimenti da due a tre. Ogni volta che chiamiamo Rc::clone, il conteggio dei riferimenti ai dati all’interno dell’Rc<List> aumenterà, e i dati non verranno puliti a meno che non ci siano zero riferimenti ad esso.

rust

use std::rc::Rc; enum List {
Cons(i32, Rc<List>),
Nil,
} use crate::List::{Cons, Nil};

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}

Dobbiamo aggiungere una dichiarazione use per portare Rc<T> nello scope perché non è nel preludio. In main, creiamo la lista che contiene 5 e 10 e la memorizziamo in un nuovo Rc<List> in a. Poi quando creiamo b e c, chiamiamo la funzione Rc::clone e passiamo un riferimento all’Rc<List> in a come argomento.

Avremmo potuto chiamare a.clone() invece di Rc::clone(&a), ma la convenzione di Rust è di usare Rc::clone in questo caso. L’implementazione di Rc::clone non fa una copia profonda di tutti i dati come fanno la maggior parte delle implementazioni di clone dei tipi. La chiamata a Rc::clone incrementa solo il conteggio dei riferimenti, il che non richiede molto tempo. Le copie profonde dei dati possono richiedere molto tempo. Utilizzando Rc::clone per il conteggio dei riferimenti, possiamo distinguere visualmente tra i cloni che fanno copie profonde e i cloni che aumentano il conteggio dei riferimenti. Quando cerchiamo problemi di prestazioni nel codice, dobbiamo considerare solo i cloni che fanno copie profonde e possiamo ignorare le chiamate a Rc::clone.

Clonare un Rc<T> Aumenta il Conteggio dei Riferimenti

Cambia

less

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("conteggio dopo aver creato a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("conteggio dopo aver creato b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("conteggio dopo aver creato c = {}", Rc::strong_count(&a));
}
println!("conteggio dopo che c esce dallo scope = {}", Rc::strong_count(&a));
}

rust

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("conteggio dopo aver creato a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("conteggio dopo aver creato b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("conteggio dopo aver creato c = {}", Rc::strong_count(&a));
}
println!("conteggio dopo che c esce dallo scope = {}", Rc::strong_count(&a));
}

In ogni punto del programma in cui il conteggio dei riferimenti cambia, stampiamo il conteggio dei riferimenti, che otteniamo chiamando la funzione Rc::strong_count. Questa funzione si chiama strong_count anziché count perché il tipo Rc<T> ha anche un weak_count; vedremo a cosa serve weak_count nella sezione “Prevenire Cicli di Riferimento: Trasformare un Rc<T> in un Weak<T>”.

Questo codice stampa quanto segue:

java

$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.45s
Running `target/debug/cons-list`
conteggio dopo aver creato a = 1
conteggio dopo aver creato b = 2
conteggio dopo aver creato c = 3
conteggio dopo che c esce dallo scope = 2

Possiamo vedere che l’Rc<List> in a ha un conteggio dei riferimenti iniziale di 1; poi ogni volta che chiamiamo clone, il conteggio aumenta di 1. Quando c esce dallo scope, il conteggio diminuisce di 1. Non dobbiamo chiamare una funzione per diminuire il conteggio dei riferimenti come dobbiamo chiamare Rc::clone per aumentare il conteggio dei riferimenti: l’implementazione del trait Drop diminuisce automaticamente il conteggio dei riferimenti quando un valore Rc<T> esce dallo scope.

Ciò che non possiamo vedere in questo esempio è che quando b e poi a escono dallo scope alla fine di main, il conteggio è poi 0, e l’Rc<List> viene pulito completamente. L’utilizzo di Rc<T> consente a un singolo valore di avere più proprietari, e il conteggio assicura che il valore rimanga valido finché esiste ancora uno qualsiasi dei proprietari.

Attraverso i riferimenti immutabili, Rc<T> ti consente di condividere dati tra più parti del tuo programma solo per la lettura. Se Rc<T> ti consentisse di avere anche più riferimenti mutabili, potresti violare una delle regole di prestito discusse nel Capitolo 4: più prestiti mutabili allo stesso posto possono causare corse di dati e inconsistenze. Ma poter mutare i dati è molto utile! Nella prossima sezione, discuteremo il pattern di mutabilità interna e il tipo RefCell<T> che puoi utilizzare in congiunzione con un Rc<T> per lavorare con questa restrizione di immutabilità.

1 Comment

Leave a Reply

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *