diff --git a/Cargo.lock b/Cargo.lock index 2bff93b7..23f3ea6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ name = "cordyceps" version = "0.2.0" dependencies = [ "loom", + "pin-project", "proptest", "tracing 0.1.34", "tracing-subscriber 0.3.11", diff --git a/cordyceps/Cargo.toml b/cordyceps/Cargo.toml index e76f1021..5553ed7b 100644 --- a/cordyceps/Cargo.toml +++ b/cordyceps/Cargo.toml @@ -27,6 +27,7 @@ no-cache-pad = [] proptest = "1" tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["fmt"] } +pin-project = "1" [target.'cfg(loom)'.dependencies] loom = "0.5.5" diff --git a/cordyceps/src/list.rs b/cordyceps/src/list.rs index 81d7bd44..d05d8c24 100644 --- a/cordyceps/src/list.rs +++ b/cordyceps/src/list.rs @@ -9,6 +9,7 @@ use core::{ fmt, marker::PhantomPinned, mem, + pin::Pin, ptr::{self, NonNull}, }; @@ -569,7 +570,7 @@ impl<'list, T: Linked> + ?Sized> IntoIterator for &'list List { } impl<'list, T: Linked> + ?Sized> IntoIterator for &'list mut List { - type Item = &'list mut T; + type Item = Pin<&'list mut T>; type IntoIter = IterMut<'list, T>; #[inline] @@ -827,7 +828,7 @@ impl<'list, T: Linked> + ?Sized> DoubleEndedIterator for Iter<'list, T> // === impl IterMut ==== impl<'list, T: Linked> + ?Sized> Iterator for IterMut<'list, T> { - type Item = &'list mut T; + type Item = Pin<&'list mut T>; fn next(&mut self) -> Option { if self.len == 0 { @@ -842,7 +843,13 @@ impl<'list, T: Linked> + ?Sized> Iterator for IterMut<'list, T> { // while the iterator exists. the returned item will not outlive the // iterator. self.curr = T::links(curr).as_ref().next(); - Some(curr.as_mut()) + + // safety: pinning the returned element is actually *necessary* to + // uphold safety invariants here. if we returned `&mut T`, the + // element could be `mem::replace`d out of the list, invalidating + // any pointers to it. thus, we *must* pin it before returning it. + let pin = Pin::new_unchecked(curr.as_mut()); + Some(pin) } } @@ -874,7 +881,13 @@ impl<'list, T: Linked> + ?Sized> DoubleEndedIterator for IterMut<'list, // while the iterator exists. the returned item will not outlive the // iterator. self.curr_back = T::links(curr).as_ref().prev(); - Some(curr.as_mut()) + + // safety: pinning the returned element is actually *necessary* to + // uphold safety invariants here. if we returned `&mut T`, the + // element could be `mem::replace`d out of the list, invalidating + // any pointers to it. thus, we *must* pin it before returning it. + let pin = Pin::new_unchecked(curr.as_mut()); + Some(pin) } } } diff --git a/cordyceps/src/list/tests.rs b/cordyceps/src/list/tests.rs index fa039a62..fcebce59 100644 --- a/cordyceps/src/list/tests.rs +++ b/cordyceps/src/list/tests.rs @@ -463,8 +463,10 @@ mod owned_entry { /// An entry type whose ownership is assigned to the list directly. #[derive(Debug)] + #[pin_project::pin_project] #[repr(C)] struct OwnedEntry { + #[pin] links: Links, val: i32, } @@ -599,9 +601,10 @@ mod owned_entry { let a = owned_entry(1); let b = owned_entry(2); let c = owned_entry(3); - fn incr_entry(entry: &mut OwnedEntry) -> i32 { - entry.val += 1; - entry.val + fn incr_entry(entry: Pin<&mut OwnedEntry>) -> i32 { + let entry = entry.project(); + *entry.val += 1; + *entry.val } let mut list = List::new();