safety: what I think is a safe macro for linked list creation

+ some docs changes and cleanup
This commit is contained in:
2025-07-20 01:36:48 +02:00
parent 0d8780017b
commit af5e5ff19e
5 changed files with 143 additions and 34 deletions

View File

@@ -2,7 +2,7 @@
//!
//! Doubly as each node points to the next and previous node.
use std::{mem::transmute, ops::Deref, pin::Pin};
use std::{marker::PhantomPinned, mem::transmute, ops::Deref, pin::Pin};
use parking_lot::RwLock;
@@ -26,28 +26,16 @@ impl<T> Default for NodeHeadInner<'_, T> {
}
}
pub struct LinkedList<'ll, T>(RwLock<NodeHeadInner<'ll, T>>);
pub struct NodeHead<'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> {
impl<T> Default for NodeHead<'_, T> {
#[must_use]
fn default() -> Self {
Self(RwLock::new(NodeHeadInner::default()))
}
}
impl<'ll, T> Deref for LinkedList<'ll, T> {
impl<'ll, T> Deref for NodeHead<'ll, T> {
type Target = RwLock<NodeHeadInner<'ll, T>>;
fn deref(&self) -> &Self::Target {
@@ -55,7 +43,7 @@ impl<'ll, T> Deref for LinkedList<'ll, T> {
}
}
impl<'ll, T> LinkedList<'ll, T> {
impl<'ll, T> NodeHead<'ll, T> {
#[must_use]
pub fn new() -> Self {
Self::default()
@@ -85,6 +73,18 @@ impl<'ll, T> LinkedList<'ll, T> {
unsafe { transmute::<&Self, &'ll Self>(myself.get_ref()) }
}
/// # Safety
///
/// Must be at the end at the end of its scope, when there's no references into it left.
pub unsafe fn manual_drop(&'ll 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
while unsafe { self.pop().is_some() } {}
}
pub fn prepend(&'ll self, data: T) {
let self_lock = self.write();
let next = self_lock.start;
@@ -135,5 +135,111 @@ impl<'ll, T> LinkedList<'ll, T> {
}
}
// Can't quite make this work how I want 😭
/// Attempt to safe wrap around a [`NodeHead`] to allow a sound API with [`Drop`] implementation.
///
/// Please see [`create_ll`] and [`del_ll`]
pub struct LinkedList<'ll, T> {
head: NodeHead<'ll, T>,
_pinned: PhantomPinned,
}
impl<'ll, T> LinkedList<'ll, T> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// # Safety
///
/// NEVER EVER EVER extend the lifetime of this beyond the end of scope of the linked list
/// itself, it's all good and safe UNTIL it's dropped.
///
/// Allows you to associate the lifetime of this to something else to prevent accidentall
/// over-extending the lifetime.
pub unsafe fn extend_for<'scope>(&self, _: &'scope impl std::any::Any) -> &'ll NodeHead<'ll, T>
where
'scope: 'll,
{
unsafe { transmute(&self.head) }
}
}
impl<T> Default for LinkedList<'_, T> {
#[must_use]
fn default() -> Self {
Self {
head: NodeHead::new(),
_pinned: PhantomPinned,
}
}
}
impl<T> Drop for LinkedList<'_, T> {
fn drop(&mut self) {
// Extend to 'static so the compiler doesn't cry, we know this covers 'll
let myself = unsafe { self.extend_for(&()) };
// And this is `Drop` so there shouldn't be any refs as the end of this function would be
// where their lifetime ('ll) ends
unsafe { myself.manual_drop() }
}
}
// I'm not so sure this is that much bulletproof but behaves sollidly enough
/// Unsafe macro that automatically creates a linked list and an extended reference.
///
/// Also creates a `scope = ()` variable at the same level as `$val` to mimic its scope and prevent
/// accidental expansion of the unsafe lifetime beyond the current scope.
///
/// For example:
///
/// ```
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// del_ll!(ll_val, ll);
/// ```
///
/// But trying to use `ll` after the deletion should fail:
///
/// ```compile_fail
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// del_ll!(ll_val, ll);
/// ll.prepend("test2".to_string());
/// ```
///
/// Or trying to expand `ll` beyond the scope it was defined in, even if not deleted:
///
/// ```compile_fail
/// use concurrent_linked_list::double::{create_ll, del_ll, LinkedList};
///
/// let ll = {
/// create_ll!(LinkedList::<String>::new(), ll_val, ll);
/// ll.prepend("test".to_string());
/// ll
/// }
/// ll.prepend("test2".to_string());
/// ```
pub macro create_ll($rhs:expr, $val:ident, $ref:ident) {
let $val = $rhs;
let scope = ();
let $ref = unsafe { $val.extend_for(&scope) };
}
/// Macro that attempts to run some higene cleanup on [`create_ll`] to avoid accidental use ot the
/// reference further too. Other functions could still extend the lifetime beyond acceptable
/// though.
pub macro del_ll($val:ident, $ref:ident) {
#[allow(unused_variables)]
let $ref = ();
drop($val);
}
#[cfg(test)]
mod tests;