Einführung
Willkommen zu Rc
In diesem Lab werden wir die Verwendung von Rc
This tutorial is from open-source community. Access the source code
💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken
Willkommen zu Rc
In diesem Lab werden wir die Verwendung von Rc
<T>
{=html}, der referenzzählende Smart PointerIn den meisten Fällen ist die Eigentumszuordnung klar: Du weißt genau, welche Variable ein bestimmter Wert besitzt. Es gibt jedoch Fälle, in denen ein einzelner Wert mehrere Besitzer haben kann. Beispielsweise in Graphen-Datenstrukturen können mehrere Kanten auf den gleichen Knoten verweisen, und dieser Knoten wird konzeptionell von allen Kanten, die auf ihn verweisen, besessen. Ein Knoten sollte nicht bereinigt werden, es sei denn, er hat keine Kanten, die auf ihn verweisen, und somit keine Besitzer.
Du musst die Mehrfachbesitzung explizit aktivieren, indem du den Rust-Typ Rc<T>
verwendest, was eine Abkürzung für Referenzzählung ist. Der Typ Rc<T>
verfolgt die Anzahl der Referenzen auf einen Wert, um zu bestimmen, ob der Wert noch in Gebrauch ist. Wenn es keine Referenzen auf einen Wert gibt, kann der Wert bereinigt werden, ohne dass sich irgendwelche Referenzen ungültig machen.
Stelle dir Rc<T>
wie einen Fernseher in einem Wohnzimmer vor. Wenn eine Person hereinkommt, um fernzusehen, schaltet sie ihn an. Andere können in den Raum kommen und fernsehen. Wenn die letzte Person den Raum verlässt, schaltet sie den Fernseher aus, weil er nicht mehr genutzt wird. Wenn jemand den Fernseher aus schaltet, während andere noch fernsehen, würde es einen Aufruhr unter den verbleibenden Fernsehzuschauern geben!
Wir verwenden den Typ Rc<T>
, wenn wir einige Daten auf dem Heap für mehrere Teile unseres Programms zu lesen planen und wir zur Compile-Zeit nicht bestimmen können, welcher Teil das Daten zuletzt verwenden wird. Wenn wir wüssten, welcher Teil zuletzt fertig wäre, könnten wir einfach diesen Teil zum Besitzer der Daten machen, und die normalen Eigentumsregeln, die zur Compile-Zeit angewendet werden, würden in Kraft treten.
Beachte, dass Rc<T>
nur für einfache Thread-Szenarien geeignet ist. Wenn wir in Kapitel 16 über die Parallelität sprechen, werden wir uns mit der Referenzzählung in multithreaded Programmen befassen.
<T>
{=html}Lassen Sie uns zurückkehren zu unserem Beispiel mit der Cons-Liste in Listing 15-5. Erinnern Sie sich, dass wir es mit Box<T>
definiert haben. Dieses Mal werden wir zwei Listen erstellen, die beide die Eigentumsgewalt über eine dritte Liste teilen. Konzeptionell sieht dies ähnlich wie in Abbildung 15-3 aus.
Abbildung 15-3: Zwei Listen, b
und c
, teilen die Eigentumsgewalt über eine dritte Liste, a
Wir werden die Liste a
erstellen, die 5
und dann 10
enthält. Dann werden wir zwei weitere Listen erstellen: b
, die mit 3
beginnt, und c
, die mit 4
beginnt. Beide Listen b
und c
werden dann fortfahren mit der ersten Liste a
, die 5
und 10
enthält. Mit anderen Worten, beide Listen werden die erste Liste, die 5
und 10
enthält, teilen.
Versuchen, dieses Szenario mit unserer Definition von List
mit Box<T>
umzusetzen, wird nicht funktionieren, wie in Listing 15-17 gezeigt.
Dateiname: src/main.rs
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
1 let b = Cons(3, Box::new(a));
2 let c = Cons(4, Box::new(a));
}
Listing 15-17: Demonstration, dass wir nicht zwei Listen mit Box<T>
haben dürfen, die versuchen, die Eigentumsgewalt über eine dritte Liste zu teilen
Wenn wir diesen Code kompilieren, erhalten wir diesen Fehler:
error[E0382]: use of moved value: `a`
--> src/main.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which
does not implement the `Copy` trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
Die Cons
-Varianten besitzen die Daten, die sie enthalten, also wenn wir die Liste b
erstellen [1], wird a
in b
bewegt und b
besitzt a
. Dann, wenn wir a
erneut verwenden möchten, wenn wir c
erstellen [2], dürfen wir es nicht, weil a
bereits bewegt wurde.
Wir könnten die Definition von Cons
ändern, um Referenzen zu halten, aber dann müssten wir Lebenszeitparameter angeben. Indem wir Lebenszeitparameter angeben, würden wir angeben, dass jedes Element in der Liste mindestens so lange existiert wie die gesamte Liste. Dies ist der Fall für die Elemente und Listen in Listing 15-17, aber nicht in jedem Szenario.
Stattdessen werden wir unsere Definition von List
ändern, um Rc<T>
anstelle von Box<T>
zu verwenden, wie in Listing 15-18 gezeigt. Jede Cons
-Variante wird jetzt einen Wert und einen Rc<T>
enthalten, der auf eine List
zeigt. Wenn wir b
erstellen, werden wir statt der Eigentumsgewalt über a
die Rc<List>
klonen, die a
hält, wodurch die Anzahl der Referenzen von eins auf zwei erhöht wird und a
und b
die Eigentumsgewalt über die Daten in dieser Rc<List>
teilen. Wir werden a
auch beim Erstellen von c
klonen, was die Anzahl der Referenzen von zwei auf drei erhöht. Jedes Mal, wenn wir Rc::clone
aufrufen, wird der Referenzzähler für die Daten innerhalb der Rc<List>
erhöht, und die Daten werden nicht bereinigt, es sei denn, es gibt keine Referenzen mehr auf sie.
Dateiname: src/main.rs
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
1 use std::rc::Rc;
fn main() {
2 let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
3 let b = Cons(3, Rc::clone(&a));
4 let c = Cons(4, Rc::clone(&a));
}
Listing 15-18: Eine Definition von List
, die Rc<T>
verwendet
Wir müssen einen use
-Befehl hinzufügen, um Rc<T>
in den Gültigkeitsbereich zu bringen [1], da es nicht im Präambel ist. In main
erstellen wir die Liste, die 5
und 10
enthält, und speichern sie in einer neuen Rc<List>
in a
[2]. Dann, wenn wir b
[3] und c
[4] erstellen, rufen wir die Rc::clone
-Funktion auf und übergeben einen Verweis auf die Rc<List>
in a
als Argument.
Wir hätten a.clone()
aufrufen können, anstatt Rc::clone(&a)
, aber die Konvention in Rust ist, in diesem Fall Rc::clone
zu verwenden. Die Implementierung von Rc::clone
macht keine Tiefe-Kopie aller Daten, wie die Implementierungen von clone
der meisten Typen es tun. Der Aufruf von Rc::clone
erhöht nur den Referenzzähler, was nicht viel Zeit benötigt. Tiefe-Kopien von Daten können viel Zeit in Anspruch nehmen. Indem wir Rc::clone
für die Referenzzählung verwenden, können wir zwischen den Tiefe-Kopie-Art von Klonen und den Klonen, die den Referenzzähler erhöhen, visuell unterscheiden. Wenn wir nach Leistungsproblemen im Code suchen, müssen wir nur die Tiefe-Kopie-Klone berücksichtigen und können Aufrufe von Rc::clone
ignorieren.
<T>
{=html} erhöht den ReferenzzählerÄndern wir unser funktionierendes Beispiel in Listing 15-18, so dass wir sehen können, wie sich die Referenzzähler ändern, wenn wir Referenzen auf die Rc<List>
in a
erstellen und fallen lassen.
In Listing 15-19 werden wir main
ändern, sodass es einen inneren Gültigkeitsbereich um die Liste c
hat; dann können wir sehen, wie sich die Referenzzähler ändert, wenn c
außerhalb des Gültigkeitsbereichs fällt.
Dateiname: src/main.rs
--snip--
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!(
"count after creating a = {}",
Rc::strong_count(&a)
);
let b = Cons(3, Rc::clone(&a));
println!(
"count after creating b = {}",
Rc::strong_count(&a)
);
{
let c = Cons(4, Rc::clone(&a));
println!(
"count after creating c = {}",
Rc::strong_count(&a)
);
}
println!(
"count after c goes out of scope = {}",
Rc::strong_count(&a)
);
}
Listing 15-19: Ausgabe des Referenzzählers
An jeder Stelle im Programm, an der sich die Referenzzähler ändert, drucken wir die Referenzzähler, die wir erhalten, indem wir die Rc::strong_count
-Funktion aufrufen. Diese Funktion heißt strong_count
statt count
, weil der Typ Rc<T>
auch eine weak_count
hat; wir werden sehen, wofür weak_count
verwendet wird in "Verhinderung von Referenzzirkeln mit Weak<T>
{=html}".
Dieser Code gibt Folgendes aus:
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
Wir können sehen, dass die Rc<List>
in a
einen anfänglichen Referenzzähler von 1 hat; dann erhöht sich der Zähler jedes Mal um 1, wenn wir clone
aufrufen. Wenn c
außerhalb des Gültigkeitsbereichs fällt, sinkt der Zähler um 1. Wir müssen keine Funktion aufrufen, um den Referenzzähler zu verringern, wie wir Rc::clone
aufrufen müssen, um den Referenzzähler zu erhöhen: Die Implementierung des Drop
-Traits verringert den Referenzzähler automatisch, wenn ein Rc<T>
-Wert außerhalb des Gültigkeitsbereichs fällt.
Was wir in diesem Beispiel nicht sehen können, ist, dass wenn b
und dann a
am Ende von main
außerhalb des Gültigkeitsbereichs fallen, der Zähler dann 0 ist und die Rc<List>
vollständig bereinigt wird. Die Verwendung von Rc<T>
ermöglicht es, dass ein einzelner Wert mehrere Besitzer hat, und der Zähler stellt sicher, dass der Wert solange gültig bleibt, wie irgendeiner der Besitzer noch existiert.
Über unveränderliche Referenzen ermöglicht Rc<T>
es Ihnen, Daten zwischen mehreren Teilen Ihres Programms nur zum Lesen zu teilen. Wenn Rc<T>
Ihnen auch mehrere veränderliche Referenzen erlauben würde, könnten Sie eine der in Kapitel 4 diskutierten Entleihregeln verletzen: mehrere veränderliche Entleihungen an die gleiche Stelle können zu Datenkonflikten und Inkonsistenzen führen. Aber das Ändern von Daten ist sehr nützlich! Im nächsten Abschnitt werden wir das Muster der inneren Veränderbarkeit und den Typ RefCell<T>
diskutieren, den Sie in Verbindung mit einem Rc<T>
verwenden können, um dieser Unveränderbarkeitsbeschränkung zu begegnen.
Herzlichen Glückwunsch! Du hast das Lab zu Rc