add drop impl
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Doubly as each node points to the next and previous node.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::{mem::transmute, ops::Deref, pin::Pin};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
@@ -26,17 +26,20 @@ impl<T> Default for NodeHeadInner<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// impl<'ll, T> Drop for LinkedList<'ll, T> {
|
||||
// fn drop(&mut self) {
|
||||
// // SAFETY: this is the very last ref of &self so it can pretty much assume no external
|
||||
// // refs into the inner data as they would be invalid to live after this
|
||||
// while unsafe { self.pop().is_some() } {}
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct LinkedList<'ll, T>(RwLock<NodeHeadInner<'ll, T>>);
|
||||
|
||||
impl<'ll, T> Drop for LinkedList<'ll, T> {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: this is the drop impl so we can guarantee the reference is valid for the
|
||||
// lifetime of the struct itself and external references would be invalidated right after
|
||||
// the [`Drop`] of it (this fn). I don't think there's a way to differenciate the lifetimes
|
||||
// by a drop implementation so this would be safe as no external references lifetimes would
|
||||
// be valid after drop finishes
|
||||
let myself = unsafe { transmute::<&mut Self, &'ll mut Self>(self) };
|
||||
while unsafe { myself.pop().is_some() } {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for LinkedList<'_, T> {
|
||||
#[must_use]
|
||||
fn default() -> Self {
|
||||
@@ -58,6 +61,30 @@ impl<'ll, T> LinkedList<'ll, T> {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The only context in which it's safe to call this when [`self`] was pinned immediately after
|
||||
/// its creation so that it can be guaranteed that the returned reference is valid for all the
|
||||
/// [`LinkedList`]'s lifetime.
|
||||
///
|
||||
/// The issue arises from the [`Drop`] implementation. It takes a `&mut` reference, that means
|
||||
/// that all previous immutable references are dropped. But most methods of the linked require
|
||||
/// you to promise the borrow of [`self`] is valid for all `'ll` and that's only true if no
|
||||
/// destructor runs. This makes [`Drop`] incompatible with the use of methods of the form
|
||||
/// `fn(&'myself self)`.
|
||||
///
|
||||
/// In turn to this, the [`Drop`] implementation must assume it's not the only reference even
|
||||
/// thought it's `&mut`. Anyhow it should only be called when the scope of [`self`] is about to
|
||||
/// end and the other references would be invalidated after the call to [`Drop`], though in
|
||||
/// reality they really have no use before that call but to guarantee the "internal" self
|
||||
/// references in the linked list remain valid through the destructor. It's kinda a really edge
|
||||
/// case in the language with shared structures that require references with lifetimes as long
|
||||
/// as self to guarantee their validity but still have [`Drop`] implementations.
|
||||
#[must_use]
|
||||
pub unsafe fn get_self_ref(myself: Pin<&Self>) -> &'ll Self {
|
||||
unsafe { transmute::<&Self, &'ll Self>(myself.get_ref()) }
|
||||
}
|
||||
|
||||
pub fn prepend(&'ll self, data: T) {
|
||||
let self_lock = self.write();
|
||||
let next = self_lock.start;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
pin::pin,
|
||||
sync::{
|
||||
Barrier,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -35,14 +36,16 @@ impl Debug for StringWithDrop {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
fn concurrency_and_scoped_drop() {
|
||||
const CREATE1: usize = 100;
|
||||
const CREATE2: usize = 100;
|
||||
const NOT_POP: usize = 20;
|
||||
|
||||
// TODO: Find out WHYTF do I need to scope this and compiler won't let me `drop`
|
||||
{
|
||||
let ll = LinkedList::<StringWithDrop>::new();
|
||||
// TODO: make this a macro or a "guard" struct that can be dropped
|
||||
let ll = pin!(LinkedList::<StringWithDrop>::new());
|
||||
let llref = unsafe { LinkedList::get_self_ref(ll.as_ref()) };
|
||||
|
||||
let barrier = Barrier::new(3);
|
||||
|
||||
thread::scope(|s| {
|
||||
@@ -50,14 +53,14 @@ fn it_works() {
|
||||
barrier.wait();
|
||||
|
||||
for n in 0..CREATE1 {
|
||||
ll.prepend(format!("A {n}").into());
|
||||
llref.prepend(format!("A {n}").into());
|
||||
}
|
||||
});
|
||||
s.spawn(|| {
|
||||
barrier.wait();
|
||||
|
||||
for n in 0..CREATE2 {
|
||||
ll.prepend(format!("B {n}").into());
|
||||
llref.prepend(format!("B {n}").into());
|
||||
}
|
||||
});
|
||||
s.spawn(|| {
|
||||
@@ -65,7 +68,7 @@ fn it_works() {
|
||||
|
||||
for _ in 0..(CREATE1 + CREATE2 - NOT_POP) {
|
||||
unsafe {
|
||||
while ll.pop().is_none() {
|
||||
while llref.pop().is_none() {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
@@ -76,6 +79,5 @@ fn it_works() {
|
||||
assert_eq!(DROP_C.load(Ordering::Relaxed), CREATE1 + CREATE2 - NOT_POP);
|
||||
}
|
||||
|
||||
// TODO: when drop is impl
|
||||
// assert_eq!(DROP_C.load(Ordering::Relaxed), CREATE1 + CREATE2);
|
||||
assert_eq!(DROP_C.load(Ordering::Relaxed), CREATE1 + CREATE2);
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![feature(arbitrary_self_types, arbitrary_self_types_pointers)]
|
||||
#![feature(
|
||||
arbitrary_self_types,
|
||||
arbitrary_self_types_pointers,
|
||||
)]
|
||||
#![warn(clippy::pedantic)]
|
||||
|
||||
#[cfg(doc)]
|
||||
|
Reference in New Issue
Block a user