Fix potential invalid memory reference.
[rust-101.git] / solutions / src / callbacks.rs
index 93fcb173148e595c17127c49b11f2abae137f777..7ff0cdb3594cab92f86801ed8eebc3360d774273 100644 (file)
@@ -16,7 +16,7 @@ impl Callbacks {
         self.callbacks.push(cell);                                  /*@*/
     }
 
-    pub fn call(&self, val: i32) {
+    pub fn call(&mut self, val: i32) {
         for callback in self.callbacks.iter() {
             // We have to *explicitly* borrow the contents of a `RefCell`.
             //@ At run-time, the cell will keep track of the number of outstanding shared and mutable borrows,
@@ -50,17 +50,27 @@ mod tests {
     #[test]
     #[should_panic]
     fn test_reentrant() {
+        // We want to create a `Callbacks` instance containing a closure referencing this very `Callbacks` instance.
+        // To create this cycle, we need to put the `Callbacks` into a `RefCell`.
         let c = Rc::new(RefCell::new(Callbacks::new()));
         c.borrow_mut().register(|val| println!("Callback called: {}", val) );
 
-        // If we change the two "borrow" below to "borrow_mut", you can get a panic even with a "call" that requires a
-        // mutable borrow. However, that panic is then triggered by our own, external `RefCell` (so it's kind of our fault),
-        // rather than being triggered by the `RefCell` in the `Callbacks`.
-        {
-            let c2 = c.clone();
-            c.borrow_mut().register(move |val| c2.borrow().call(val+val) );
-        }
+        // This adds the cyclic closure, which refers to the `Callbacks` though `c2`.
+        let c2 = c.clone();
+        c.borrow_mut().register(move |val| {
+            // This `borrow_mut` won't fail because we are careful below to close the `RefCell`
+            // before triggering the cycle. You can see that this is the case because the log message
+            // below is printed.
+            let mut guard = c2.borrow_mut();
+            println!("Callback called with {}, ready to go for nested call.", val);
+            guard.call(val+val)
+        } );
 
-        c.borrow().call(42);
+        // We do a clone of the `Callbacks` to ensure that the `RefCell` we created for the cycle is closed.
+        // This makes sure that it's not our `borrow_mut` above that complains about two mutable borrows,
+        // but rather the one inside `Callbacks::call`.
+        let mut c2: Callbacks = c.borrow().clone();
+        drop(c); // This is not strictly necessary. It demonstrates that we are not holding any reference to the `RefCell` any more.
+        c2.call(42);
     }
-}
\ No newline at end of file
+}