Einführung
Willkommen zu RefCell
In diesem Lab werden wir das Konzept der inneren Veränderbarkeit in Rust erkunden und wie es mit dem Typ RefCell<T>
implementiert wird.
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 RefCell
In diesem Lab werden wir das Konzept der inneren Veränderbarkeit in Rust erkunden und wie es mit dem Typ RefCell<T>
implementiert wird.
<T>
und das Muster der inneren VeränderbarkeitInterne Veränderbarkeit ist ein Entwurfsmuster in Rust, das es Ihnen ermöglicht, Daten zu mutieren, auch wenn es unveränderte Referenzen auf diese Daten gibt; normalerweise ist diese Aktion durch die Leihregeln verboten. Um Daten zu mutieren, verwendet das Muster unsafe
-Code innerhalb einer Datenstruktur, um Rusts übliche Regeln, die die Mutation und das Entleihen regeln, zu umgehen. Unsafe-Code signalisiert dem Compiler, dass wir die Regeln manuell überprüfen, anstatt auf den Compiler zu verlassen, um sie für uns zu überprüfen; wir werden unsafe-Code im Kapitel 19 ausführlicher besprechen.
Wir können nur dann Typen verwenden, die das Muster der inneren Veränderbarkeit verwenden, wenn wir gewährleisten können, dass die Leihregeln zur Laufzeit eingehalten werden, obwohl der Compiler dies nicht gewährleisten kann. Der involvierte unsafe
-Code wird dann in eine sichere API eingewickelt, und der äußere Typ bleibt immer noch unveränderlich.
Lassen Sie uns dieses Konzept erkunden, indem wir uns den Typ RefCell<T>
ansehen, der das Muster der inneren Veränderbarkeit folgt.
<T>
Im Gegensatz zu Rc<T>
repräsentiert der Typ RefCell<T>
die alleinige Eigentumsverhältnisse über die von ihm gehaltenen Daten. Was unterscheidet RefCell<T>
also von einem Typ wie Box<T>
? Erinnern Sie sich an die Leihregeln, die Sie im Kapitel 4 gelernt haben:
Bei Referenzen und Box<T>
werden die Invarianten der Leihregeln zur Compilezeit überprüft. Bei RefCell<T>
werden diese Invarianten zur Laufzeit überprüft. Bei Referenzen erhalten Sie bei Verstoß gegen diese Regeln einen Compilerfehler. Bei RefCell<T>
wird Ihr Programm bei einem Verstoß gegen diese Regeln abstürzen und beenden.
Die Vorteile der Überprüfung der Leihregeln zur Compilezeit sind, dass Fehler früher im Entwicklungsprozess erkannt werden und dass die Laufzeitleistung nicht beeinträchtigt wird, da alle Analysen im Voraus abgeschlossen sind. Aus diesen Gründen ist die Überprüfung der Leihregeln zur Compilezeit in den meisten Fällen die beste Wahl, weshalb dies die Standardmethode von Rust ist.
Der Vorteil der Überprüfung der Leihregeln zur Laufzeit ist dagegen, dass bestimmte sicherheitsrelevante Szenarien dann möglich sind, die durch die Compilezeitprüfungen verboten wären. Statische Analysen wie der Rust-Compiler sind von Natur aus konservativ. Einige Eigenschaften von Code sind unmöglich zu erkennen, indem man den Code analysiert: Das berühmteste Beispiel ist das Halteproblem, das außerhalb des Rahmens dieses Buches liegt, aber ein interessantes Thema für die Recherche.
Da einige Analysen unmöglich sind, kann der Rust-Compiler möglicherweise ein korrektes Programm ablehnen, wenn er nicht sicher ist, dass der Code den Eigentumsregeln entspricht; auf diese Weise ist er konservativ. Wenn Rust ein fehlerhaftes Programm akzeptierte, könnten die Benutzer nicht auf die Garantien vertrauen, die Rust gibt. Wenn Rust jedoch ein korrektes Programm ablehnt, wird der Programmierer belastet, aber nichts Katastrophales kann passieren. Der Typ RefCell<T>
ist nützlich, wenn Sie sicher sind, dass Ihr Code den Leihregeln folgt, aber der Compiler nicht in der Lage ist, dies zu verstehen und zu gewährleisten.
Ähnlich wie Rc<T>
ist RefCell<T>
nur für die Verwendung in einthreadigen Szenarien vorgesehen und gibt Ihnen einen Compilerfehler, wenn Sie versuchen, es in einem mehrthreadigen Kontext zu verwenden. Wir werden im Kapitel 16 darüber sprechen, wie man die Funktionalität von RefCell<T>
in einem mehrthreadigen Programm erhalten kann.
Hier ist eine Zusammenfassung der Gründe, warum man Box<T>
, Rc<T>
oder RefCell<T>
wählt:
Rc<T>
ermöglicht mehrere Besitzer der gleichen Daten; Box<T>
und RefCell<T>
haben jeweils einen Besitzer.Box<T>
erlaubt unveränderbare oder veränderbare Leihvorgänge, die zur Compilezeit überprüft werden; Rc<T>
erlaubt nur unveränderbare Leihvorgänge, die zur Compilezeit überprüft werden; RefCell<T>
erlaubt unveränderbare oder veränderbare Leihvorgänge, die zur Laufzeit überprüft werden.RefCell<T>
veränderbare Leihvorgänge zur Laufzeit erlaubt, können Sie den Wert innerhalb von RefCell<T>
mutieren, auch wenn RefCell<T>
unveränderbar ist.Das Mutieren des Werts innerhalb eines unveränderbaren Werts ist das Muster der inneren Veränderbarkeit. Betrachten wir eine Situation, in der die innere Veränderbarkeit nützlich ist, und untersuchen, wie dies möglich ist.
Eine Folge der Leihregeln ist, dass Sie, wenn Sie einen unveränderbaren Wert haben, diesen nicht veränderbar leihen können. Beispielsweise wird dieser Code nicht kompilieren:
Dateiname: src/main.rs
fn main() {
let x = 5;
let y = &mut x;
}
Wenn Sie diesen Code versuchen zu kompilieren, erhalten Sie den folgenden Fehler:
error[E0596]: cannot borrow `x` as mutable, as it is not declared
as mutable
--> src/main.rs:3:13
|
2 | let x = 5;
| - help: consider changing this to be mutable: `mut x`
3 | let y = &mut x;
| ^^^^^^ cannot borrow as mutable
Es gibt jedoch Situationen, in denen es nützlich wäre, wenn ein Wert sich in seinen Methoden verändert, aber für anderen Code unveränderbar erscheint. Code außerhalb der Methoden des Werts wäre nicht in der Lage, den Wert zu mutieren. Das Verwenden von RefCell<T>
ist eine Möglichkeit, die Fähigkeit zur internen Veränderbarkeit zu erhalten, aber RefCell<T>
umgeht die Leihregeln nicht vollständig: Der Leihprüfungsmechanismus im Compiler erlaubt diese interne Veränderbarkeit, und die Leihregeln werden zur Laufzeit überprüft. Wenn Sie die Regeln verletzen, erhalten Sie einen panic!
statt eines Compilerfehlers.
Betrachten wir ein praktisches Beispiel, in dem wir RefCell<T>
verwenden können, um einen unveränderbaren Wert zu mutieren, und sehen, warum das nützlich ist.
Manchmal verwendet ein Programmierer während des Tests einen Typ anstelle eines anderen Typs, um bestimmte Verhaltensweisen zu beobachten und sicherzustellen, dass diese korrekt implementiert sind. Dieser Platzhaltertyp wird als Testdoublé bezeichnet. Man kann sich das in etwa so vorstellen wie einen Stuntman in der Filmproduktion, der für einen Schauspieler tritt, um eine besonders schwierige Szene zu filmen. Testdoublés vertreten andere Typen, wenn wir Tests ausführen. Mock-Objekte sind spezielle Arten von Testdoublés, die aufzeichnen, was während eines Tests passiert, sodass Sie feststellen können, ob die richtigen Aktionen stattgefunden haben.
Rust hat keine Objekte im selben Sinne wie andere Sprachen, und das Standardbibliothek von Rust hat keine eingebautes Mock-Objekt-Funktionalität wie einige andere Sprachen. Allerdings können Sie definitiv eine Struktur erstellen, die die gleichen Zwecke wie ein Mock-Objekt erfüllt.
Hier ist die Szene, die wir testen werden: Wir werden eine Bibliothek erstellen, die einen Wert im Vergleich zu einem maximalen Wert verfolgt und Nachrichten sendet, je nachdem, wie nah der aktuelle Wert am maximalen Wert ist. Diese Bibliothek könnte beispielsweise verwendet werden, um die Quote der Anzahl der API-Aufrufe zu verfolgen, die ein Benutzer tätigen darf.
Unsere Bibliothek wird nur die Funktionalität zur Verfügung stellen, um zu verfolgen, wie nah ein Wert am maximalen Wert ist und welche Nachrichten zu welchen Zeiten gesendet werden sollten. Anwendungen, die unsere Bibliothek verwenden, sollen das Mechanismus zum Senden der Nachrichten bereitstellen: Die Anwendung könnte eine Nachricht in der Anwendung ablegen, eine E-Mail senden, eine SMS senden oder etwas anderes tun. Die Bibliothek muss diese Details nicht kennen. Alles, was sie benötigt, ist etwas, das ein von uns bereitgestelltes Merkmal namens Messenger
implementiert. Listing 15-20 zeigt den Code der Bibliothek.
Dateiname: src/lib.rs
pub trait Messenger {
1 fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(
messenger: &'a T,
max: usize
) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
2 pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max =
self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger
.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent: You're at 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You're at 75% of your quota!");
}
}
}
Listing 15-20: Eine Bibliothek, um zu verfolgen, wie nah ein Wert am maximalen Wert ist und eine Warnung auszugeben, wenn der Wert auf bestimmten Niveaus ist
Ein wichtiger Teil dieses Codes ist, dass das Messenger
-Merkmal eine Methode namens send
hat, die eine unveränderbare Referenz auf self
und den Text der Nachricht annimmt [1]. Dieses Merkmal ist die Schnittstelle, die unser Mock-Objekt implementieren muss, damit das Mock auf die gleiche Weise wie ein echtes Objekt verwendet werden kann. Der andere wichtige Teil ist, dass wir das Verhalten der set_value
-Methode auf dem LimitTracker
testen möchten [2]. Wir können das, was wir für den value
-Parameter übergeben, ändern, aber set_value
gibt nichts zurück, worauf wir uns als Behauptung verlassen können. Wir möchten in der Lage sein zu sagen, dass wenn wir einen LimitTracker
mit etwas erstellen, das das Messenger
-Merkmal implementiert und einen bestimmten Wert für max
, wenn wir verschiedene Zahlen für value
übergeben, der Messenger dazu aufgefordert wird, die entsprechenden Nachrichten zu senden.
Wir brauchen ein Mock-Objekt, das statt einer E-Mail oder SMS zu senden, wenn wir send
aufrufen, nur die Nachrichten verfolgen wird, die es aufgefordert wird zu senden. Wir können eine neue Instanz des Mock-Objekts erstellen, einen LimitTracker
erstellen, der das Mock-Objekt verwendet, die set_value
-Methode auf LimitTracker
aufrufen und dann überprüfen, ob das Mock-Objekt die Nachrichten hat, die wir erwarten. Listing 15-21 zeigt einen Versuch, ein Mock-Objekt zu implementieren, um genau das zu tun, aber der Leihprüfungsmechanismus wird es nicht zulassen.
Dateiname: src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
1 struct MockMessenger {
2 sent_messages: Vec<String>,
}
impl MockMessenger {
3 fn new() -> MockMessenger {
MockMessenger {
sent_messages: vec![],
}
}
}
4 impl Messenger for MockMessenger {
fn send(&self, message: &str) {
5 self.sent_messages.push(String::from(message));
}
}
#[test]
6 fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(
&mock_messenger,
100
);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}
Listing 15-21: Ein Versuch, ein MockMessenger
zu implementieren, das nicht vom Leihprüfungsmechanismus zugelassen wird
Dieser Testcode definiert eine MockMessenger
-Struktur [1], die ein sent_messages
-Feld mit einem Vec
von String
-Werten hat [2], um die Nachrichten zu verfolgen, die es aufgefordert wird zu senden. Wir definieren auch eine assoziierte Funktion new
[3], um es bequem zu machen, neue MockMessenger
-Werte zu erstellen, die mit einer leeren Liste von Nachrichten beginnen. Wir implementieren dann das Messenger
-Merkmal für MockMessenger
[4], sodass wir ein MockMessenger
einem LimitTracker
geben können. In der Definition der send
-Methode [5] nehmen wir die als Parameter übergebene Nachricht entgegen und speichern sie in der MockMessenger
-Liste von sent_messages
.
Im Test testen wir, was passiert, wenn der LimitTracker
aufgefordert wird, value
auf etwas zu setzen, das mehr als 75 Prozent des max
-Werts ist [6]. Zunächst erstellen wir ein neues MockMessenger
, das mit einer leeren Liste von Nachrichten beginnen wird. Dann erstellen wir einen neuen LimitTracker
und geben ihm eine Referenz auf das neue MockMessenger
und einen max
-Wert von 100
. Wir rufen die set_value
-Methode auf dem LimitTracker
mit einem Wert von 80
auf, was mehr als 75 Prozent von 100 ist. Dann stellen wir sicher, dass die Liste der Nachrichten, die das MockMessenger
verfolgt, jetzt eine Nachricht enthalten sollte.
Allerdings gibt es ein Problem mit diesem Test, wie hier gezeigt:
error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a
`&` reference
--> src/lib.rs:58:13
|
2 | fn send(&self, msg: &str);
| ----- help: consider changing that to be a mutable reference:
`&mut self`
...
58 | self.sent_messages.push(String::from(message));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a
`&` reference, so the data it refers to cannot be borrowed as mutable
Wir können das MockMessenger
nicht ändern, um die Nachrichten zu verfolgen, weil die send
-Methode eine unveränderbare Referenz auf self
annimmt. Wir können auch nicht den Vorschlag aus der Fehlermeldung folgen und &mut self
verwenden, weil dann die Signatur von send
nicht mit der Signatur in der Messenger
-Merkmaldefinition übereinstimmen würde (versuchen Sie es gerne und sehen Sie, welche Fehlermeldung Sie erhalten).
Dies ist eine Situation, in der die interne Veränderbarkeit helfen kann! Wir werden die sent_messages
in einer RefCell<T>
speichern, und dann wird die send
-Methode in der Lage sein, sent_messages
zu ändern, um die Nachrichten zu speichern, die wir gesehen haben. Listing 15-22 zeigt, wie das aussieht.
Dateiname: src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct MockMessenger {
1 sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
2 sent_messages: RefCell::new(vec![]),
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages
3.borrow_mut()
.push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
--snip--
assert_eq!(
4 mock_messenger.sent_messages.borrow().len(),
1
);
}
}
Listing 15-22: Verwenden von RefCell<T>
zum Mutieren eines inneren Werts, während der äußere Wert als unveränderbar betrachtet wird
Das sent_messages
-Feld ist jetzt vom Typ RefCell<Vec<String>>
[1] anstelle von Vec<String>
. In der new
-Funktion erstellen wir eine neue RefCell<Vec<String>>
-Instanz um den leeren Vektor [2].
Für die Implementierung der send
-Methode ist der erste Parameter immer noch eine unveränderbare Referenz auf self
, was der Merkmaldefinition entspricht. Wir rufen borrow_mut
auf der RefCell<Vec<String>>
in self.sent_messages
auf [3], um eine veränderbare Referenz auf den Wert innerhalb der RefCell<Vec<String>>
, also auf den Vektor, zu erhalten. Dann können wir push
auf der veränderbaren Referenz auf den Vektor aufrufen, um die Nachrichten während des Tests zu verfolgen.
Die letzte Änderung, die wir vornehmen müssen, ist in der Behauptung: Um zu sehen, wie viele Elemente im inneren Vektor sind, rufen wir borrow
auf der RefCell<Vec<String>>
auf, um eine unveränderbare Referenz auf den Vektor zu erhalten [4].
Jetzt, nachdem Sie gesehen haben, wie man RefCell<T>
verwendet, lassen Sie uns untersuchen, wie es funktioniert!
<T>
Beim Erstellen unveränderbarer und veränderbarer Referenzen verwenden wir die &
- und &mut
-Syntax entsprechend. Mit RefCell<T>
verwenden wir die Methoden borrow
und borrow_mut
, die Teil der sicheren API von RefCell<T>
sind. Die borrow
-Methode gibt den Smart-Pointer-Typ Ref<T>
zurück, und borrow_mut
gibt den Smart-Pointer-Typ RefMut<T>
zurück. Beide Typen implementieren Deref
, sodass wir sie wie reguläre Referenzen behandeln können.
RefCell<T>
verfolgt, wie viele Smart-Pointer vom Typ Ref<T>
und RefMut<T>
aktuell aktiv sind. Jedes Mal, wenn wir borrow
aufrufen, erhöht RefCell<T>
die Anzahl der aktiven unveränderbaren Leihvorgänge. Wenn ein Ref<T>
-Wert außerhalb des Gültigkeitsbereichs gelangt, geht die Anzahl der unveränderbaren Leihvorgänge um 1 zurück. Genau wie die Leihregeln zur Compilezeit lässt RefCell<T>
uns zu jedem Zeitpunkt viele unveränderbare Leihvorgänge oder einen veränderbaren Leihvorgang haben.
Wenn wir versuchen, diese Regeln zu verletzen, wird die Implementierung von RefCell<T>
statt eines Compilerfehlers wie bei Referenzen zur Laufzeit einen panic!
auslösen. Listing 15-23 zeigt eine Änderung der Implementierung von send
in Listing 15-22. Wir versuchen absichtlich, zwei veränderbare Leihvorgänge für denselben Gültigkeitsbereich zu erstellen, um zu zeigen, dass RefCell<T>
uns dies zur Laufzeit verhindert.
Dateiname: src/lib.rs
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}
Listing 15-23: Erstellen von zwei veränderbaren Referenzen im selben Gültigkeitsbereich, um zu sehen, dass RefCell<T>
einen panic!
auslösen wird
Wir erstellen eine Variable one_borrow
für den von borrow_mut
zurückgegebenen Smart-Pointer vom Typ RefMut<T>
. Dann erstellen wir in der gleichen Weise einen weiteren veränderbaren Leihvorgang in der Variable two_borrow
. Dies erzeugt zwei veränderbare Referenzen im selben Gültigkeitsbereich, was nicht erlaubt ist. Wenn wir die Tests für unsere Bibliothek ausführen, wird der Code in Listing 15-23 ohne Fehler kompilieren, aber der Test wird fehlschlagen:
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'main' panicked at 'already borrowed: BorrowMutError', src/lib.rs:60:53
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Beachten Sie, dass der Code mit der Fehlermeldung already borrowed: BorrowMutError
einen panic!
ausgelöst hat. So behandelt RefCell<T>
Verletzungen der Leihregeln zur Laufzeit.
Das Entscheiden, Leihfehler zur Laufzeit statt zur Compilezeit zu fangen, wie wir es hier getan haben, bedeutet, dass Sie möglicherweise Fehler in Ihrem Code erst später im Entwicklungsprozess finden: möglicherweise erst, wenn Ihr Code in der Produktion bereitgestellt wird. Außerdem würde Ihr Code aufgrund der Verfolgung der Leihvorgänge zur Laufzeit statt zur Compilezeit einen geringen Leistungsverlust bei der Laufzeit aufweisen. Allerdings ermöglicht das Verwenden von RefCell<T>
, ein Mock-Objekt zu schreiben, das sich selbst modifizieren kann, um die Nachrichten zu verfolgen, die es gesehen hat, während Sie es in einem Kontext verwenden, in dem nur unveränderbare Werte erlaubt sind. Sie können RefCell<T>
trotz seiner Nachteile verwenden, um mehr Funktionalität zu erhalten als reguläre Referenzen bieten.
<T>
und RefCell<T>
Eine häufige Weise, RefCell<T>
zu verwenden, ist in Kombination mit Rc<T>
. Erinnern Sie sich, dass Rc<T>
Ihnen ermöglicht, mehrere Besitzer eines bestimmten Datensatzes zu haben, aber es gibt nur unveränderlichen Zugang zu diesem Datensatz. Wenn Sie ein Rc<T>
haben, das ein RefCell<T>
enthält, können Sie einen Wert erhalten, der mehrere Besitzer haben und den Sie mutieren können!
Zum Beispiel erinnern Sie sich an das Beispiel der Kons-Liste in Listing 15-18, in dem wir Rc<T>
verwendet haben, um mehreren Listen die Möglichkeit zu geben, die Eigentumsverhältnisse einer anderen Liste zu teilen. Da Rc<T>
nur unveränderliche Werte hält, können wir keine der Werte in der Liste ändern, nachdem wir sie erstellt haben. Fügen wir RefCell<T>
hinzu, um seine Fähigkeit, die Werte in den Listen zu ändern. Listing 15-24 zeigt, dass wir, indem wir ein RefCell<T>
in der Cons
-Definition verwenden, den gespeicherten Wert in allen Listen ändern können.
Dateiname: src/main.rs
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
1 let value = Rc::new(RefCell::new(5));
2 let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
3 *value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
Listing 15-24: Verwenden von Rc<RefCell<i32>>
zum Erstellen einer List
, die wir mutieren können
Wir erstellen einen Wert, der eine Instanz von Rc<RefCell<i32>>
ist, und speichern ihn in einer Variable namens value
[1], damit wir ihn später direkt zugreifen können. Dann erstellen wir eine List
in a
mit einer Cons
-Variante, die value
enthält [2]. Wir müssen value
klonen, damit sowohl a
als auch value
die Eigentumsverhältnisse des inneren 5
-Werts haben, anstatt die Eigentumsverhältnisse von value
an a
zu übertragen oder a
von value
zu entleihen.
Wir umschließen die Liste a
in einem Rc<T>
, sodass wir, wenn wir Listen b
und c
erstellen, beide auf a
verweisen können, wie wir es in Listing 15-18 getan haben.
Nachdem wir die Listen in a
, b
und c
erstellt haben, möchten wir dem Wert in value
10 hinzufügen [3]. Wir tun dies, indem wir borrow_mut
auf value
aufrufen, was die automatische Dereferenzierungsmöglichkeit verwendet, die wir in "Wo ist der -> Operator?" diskutiert haben, um die Rc<T>
auf den inneren RefCell<T>
-Wert umzudeuten. Die borrow_mut
-Methode gibt einen Smart-Pointer vom Typ RefMut<T>
zurück, und wir verwenden den Dereferenzierungsoperator darauf und ändern den inneren Wert.
Wenn wir a
, b
und c
ausgeben, können wir sehen, dass alle den modifizierten Wert von 15
anstelle von 5
haben:
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
Diese Technik ist ziemlich praktisch! Indem wir RefCell<T>
verwenden, haben wir einen äußerlich unveränderlichen List
-Wert. Aber wir können die Methoden auf RefCell<T>
verwenden, die den Zugang zu seiner internen Veränderbarkeit ermöglichen, sodass wir unsere Daten ändern können, wenn wir dies benötigen. Die Laufzeitprüfungen der Leihregeln schützen uns vor Datenkonflikten, und es lohnt sich manchmal, etwas an Geschwindigkeit für diese Flexibilität in unseren Datenstrukturen zu opfern. Beachten Sie, dass RefCell<T>
für mehrthreadigen Code nicht funktioniert! Mutex<T>
ist die threadsichere Version von RefCell<T>
, und wir werden Mutex<T>
im Kapitel 16 diskutieren.
Herzlichen Glückwunsch! Sie haben das RefCell