feat: unsafely support ?Sized

This commit is contained in:
2025-07-21 20:34:09 +02:00
parent 1b2c8c01a0
commit 64f503633d
6 changed files with 177 additions and 104 deletions

10
Cargo.lock generated
View File

@@ -24,9 +24,15 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
name = "concurrent-linked-list" name = "concurrent-linked-list"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dst-clone",
"parking_lot", "parking_lot",
] ]
[[package]]
name = "dst-clone"
version = "0.1.0"
source = "git+https://git.javalsai.tuxcord.net/tuxcord/dst-clone#6a32bfa59455adafde07bafd3bc9f2182cd5127b"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.174" version = "0.2.174"
@@ -68,9 +74,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.13" version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]

View File

@@ -4,4 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
dst-clone = { git = "https://git.javalsai.tuxcord.net/tuxcord/dst-clone", version = "0" }
parking_lot = { version = "0", default-features = false } parking_lot = { version = "0", default-features = false }

View File

@@ -6,7 +6,7 @@ use std::{marker::PhantomPinned, mem::transmute, ops::Deref, pin::Pin};
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::double::node::{BackNodeWriteLock, Node, NodeBackPtr}; use crate::double::node::{BackNodeWriteLock, Node};
pub mod node; pub mod node;
@@ -16,26 +16,26 @@ pub mod node;
// case the task holding the left lock is moving rightwards. // case the task holding the left lock is moving rightwards.
// Rightwards locking can be blocking. // Rightwards locking can be blocking.
pub struct NodeHeadInner<'ll, T> { pub struct NodeHeadInner<'ll, T: ?Sized> {
start: Option<&'ll node::Node<'ll, T>>, start: Option<&'ll node::Node<'ll, T>>,
} }
impl<T> Default for NodeHeadInner<'_, T> { impl<T: ?Sized> Default for NodeHeadInner<'_, T> {
fn default() -> Self { fn default() -> Self {
Self { start: None } Self { start: None }
} }
} }
pub struct NodeHead<'ll, T>(RwLock<NodeHeadInner<'ll, T>>); pub struct NodeHead<'ll, T: ?Sized>(RwLock<NodeHeadInner<'ll, T>>);
impl<T> Default for NodeHead<'_, T> { impl<T: ?Sized> Default for NodeHead<'_, T> {
#[must_use] #[must_use]
fn default() -> Self { fn default() -> Self {
Self(RwLock::new(NodeHeadInner::default())) Self(RwLock::new(NodeHeadInner::default()))
} }
} }
impl<'ll, T> Deref for NodeHead<'ll, T> { impl<'ll, T: ?Sized> Deref for NodeHead<'ll, T> {
type Target = RwLock<NodeHeadInner<'ll, T>>; type Target = RwLock<NodeHeadInner<'ll, T>>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -43,7 +43,7 @@ impl<'ll, T> Deref for NodeHead<'ll, T> {
} }
} }
impl<'ll, T> NodeHead<'ll, T> { impl<'ll, T: ?Sized> NodeHead<'ll, T> {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@@ -85,13 +85,30 @@ impl<'ll, T> NodeHead<'ll, T> {
while unsafe { self.pop().is_some() } {} while unsafe { self.pop().is_some() } {}
} }
pub fn prepend(&'ll self, data: T) { pub fn prepend(&'ll self, data: T) -> &'ll Node<'ll, T>
where
T: Sized,
{
let self_lock = self.write(); let self_lock = self.write();
let next = self_lock.start; let next = self_lock.start;
let next_lock = next.map(|n| n.write()); let next_lock = next.map(|n| n.ptrs.write());
let new_node = Node::new_leaked(data, NodeBackPtr::new_head(self), next); let new_node = Node::new_leaked(data, node::NodeBackPtr::new_head(self), next);
// SAFETY: ptrs are surrounding and they've been locked all along // SAFETY: ptrs are surrounding and they've been locked all along
unsafe { new_node.integrate((BackNodeWriteLock::Head(self_lock), next_lock)) }; unsafe { new_node.integrate((BackNodeWriteLock::Head(self_lock), next_lock)) };
new_node
}
pub fn prepend_dst(&'ll self, data: &T) -> &'ll Node<'ll, T>
where
T: dst_clone::DstClone,
{
let self_lock = self.write();
let next = self_lock.start;
let next_lock = next.map(|n| n.ptrs.write());
let new_node = Node::leaked_from_dst(data, node::NodeBackPtr::new_head(self), next);
// SAFETY: ptrs are surrounding and they've been locked all along
unsafe { new_node.integrate((BackNodeWriteLock::Head(self_lock), next_lock)) };
new_node
} }
/// Returns [`None`] if there's no next node. /// Returns [`None`] if there's no next node.
@@ -102,8 +119,8 @@ impl<'ll, T> NodeHead<'ll, T> {
pub unsafe fn pop(&'ll self) -> Option<()> { pub unsafe fn pop(&'ll self) -> Option<()> {
let self_lock = self.write(); let self_lock = self.write();
let pop_node = self_lock.start?; let pop_node = self_lock.start?;
let pop_node_lock = pop_node.write(); let pop_node_lock = pop_node.ptrs.write();
let next_node_lock = pop_node_lock.next.map(|n| n.write()); let next_node_lock = pop_node_lock.next.map(|n| n.ptrs.write());
// SAFETY: locked all along and consecutive nodes // SAFETY: locked all along and consecutive nodes
unsafe { unsafe {
@@ -115,6 +132,7 @@ impl<'ll, T> NodeHead<'ll, T> {
drop(pop_node_lock); drop(pop_node_lock);
// SAFETY: node has been isolated so no references out // SAFETY: node has been isolated so no references out
// TODO: return a droppable guard with a ptr to the node instead
unsafe { pop_node.wait_free() } unsafe { pop_node.wait_free() }
Some(()) Some(())
@@ -127,8 +145,8 @@ impl<'ll, T> NodeHead<'ll, T> {
let mut total = Vec::new(); let mut total = Vec::new();
let mut next_node = self.read().start; let mut next_node = self.read().start;
while let Some(node) = next_node { while let Some(node) = next_node {
let read = node.read(); let read = node.ptrs.read();
total.push(read.data.clone()); total.push(node.data.clone());
next_node = read.next; next_node = read.next;
} }
total total
@@ -140,12 +158,12 @@ impl<'ll, T> NodeHead<'ll, T> {
/// Attempt to safe wrap around a [`NodeHead`] to allow a sound API with [`Drop`] implementation. /// Attempt to safe wrap around a [`NodeHead`] to allow a sound API with [`Drop`] implementation.
/// ///
/// Please see [`create_ll`] and [`del_ll`] /// Please see [`create_ll`] and [`del_ll`]
pub struct LinkedList<'ll, T> { pub struct LinkedList<'ll, T: ?Sized> {
head: NodeHead<'ll, T>, head: NodeHead<'ll, T>,
_pinned: PhantomPinned, _pinned: PhantomPinned,
} }
impl<'ll, T> LinkedList<'ll, T> { impl<'ll, T: ?Sized> LinkedList<'ll, T> {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@@ -166,7 +184,7 @@ impl<'ll, T> LinkedList<'ll, T> {
} }
} }
impl<T> Default for LinkedList<'_, T> { impl<T: ?Sized> Default for LinkedList<'_, T> {
#[must_use] #[must_use]
fn default() -> Self { fn default() -> Self {
Self { Self {
@@ -176,7 +194,7 @@ impl<T> Default for LinkedList<'_, T> {
} }
} }
impl<T> Drop for LinkedList<'_, T> { impl<T: ?Sized> Drop for LinkedList<'_, T> {
fn drop(&mut self) { fn drop(&mut self) {
// Extend to 'static so the compiler doesn't cry, we know this covers 'll // Extend to 'static so the compiler doesn't cry, we know this covers 'll
let myself = unsafe { self.extend_for(&()) }; let myself = unsafe { self.extend_for(&()) };
@@ -227,8 +245,8 @@ impl<T> Drop for LinkedList<'_, T> {
/// ll.prepend("test2".to_string()); /// ll.prepend("test2".to_string());
/// ``` /// ```
pub macro create_ll($rhs:expr, $val:ident, $ref:ident) { pub macro create_ll($rhs:expr, $val:ident, $ref:ident) {
let $val = $rhs;
let scope = (); let scope = ();
let $val = $rhs;
let $ref = unsafe { $val.extend_for(&scope) }; let $ref = unsafe { $val.extend_for(&scope) };
} }

View File

@@ -1,13 +1,15 @@
use std::{ops::Deref, ptr}; use std::{
alloc::{Layout, alloc, handle_alloc_error},
ops::Deref,
ptr::{self, metadata},
};
use parking_lot::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard}; use parking_lot::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard};
use super::NodeHeadInner; use super::NodeHeadInner;
#[repr(transparent)]
pub struct Node<'ll, T>(RwLock<NodeInner<'ll, T>>);
type NodeHead<'ll, T> = RwLock<NodeHeadInner<'ll, T>>; type NodeHead<'ll, T> = RwLock<NodeHeadInner<'ll, T>>;
pub enum NodeBackPtr<'ll, T> { pub enum NodeBackPtr<'ll, T: ?Sized> {
Head(&'ll NodeHead<'ll, T>), Head(&'ll NodeHead<'ll, T>),
Node(&'ll Node<'ll, T>), Node(&'ll Node<'ll, T>),
} }
@@ -15,27 +17,18 @@ pub enum NodeBackPtr<'ll, T> {
// yes the whole purpose is docs, might add Isolated Guards around nodes here // yes the whole purpose is docs, might add Isolated Guards around nodes here
pub mod topology_safety; pub mod topology_safety;
// TODO: RwLock the ptrs only instead of the node pub struct NodePtrPair<'ll, T: ?Sized> {
// Box<(RwLock<(&prev, &next)>, T)> pub prev: NodeBackPtr<'ll, T>,
// instead of pub next: Option<&'ll Node<'ll, T>>,
// Box<RwLock<(&prev, &next, T)>> }
// allows user to opt out of RwLock, allowing changes to adyacent nodes while T is being externally
// used and enables T: ?Sized #[repr(C)]
pub struct NodeInner<'ll, T> { pub struct Node<'ll, T: ?Sized> {
pub(crate) prev: NodeBackPtr<'ll, T>, pub ptrs: RwLock<NodePtrPair<'ll, T>>,
pub(crate) next: Option<&'ll Node<'ll, T>>,
pub data: T, pub data: T,
} }
impl<'ll, T> Deref for Node<'ll, T> { impl<T: ?Sized> Deref for Node<'_, T> {
type Target = RwLock<NodeInner<'ll, T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> Deref for NodeInner<'_, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -43,28 +36,27 @@ impl<T> Deref for NodeInner<'_, T> {
} }
} }
impl<T> Copy for NodeBackPtr<'_, T> {} impl<T: ?Sized> Copy for NodeBackPtr<'_, T> {}
impl<T> Clone for NodeBackPtr<'_, T> { impl<T: ?Sized> Clone for NodeBackPtr<'_, T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
type WriteAndBackDoublet<'ll, T> = ( type WriteAndBackDoublet<'ll, T> = (
BackNodeWriteLock<'ll, T>, BackNodeWriteLock<'ll, T>,
RwLockUpgradableReadGuard<'ll, NodeInner<'ll, T>>, RwLockUpgradableReadGuard<'ll, NodePtrPair<'ll, T>>,
); );
type WriteSurroundTriplet<'ll, T> = ( type WriteSurroundTriplet<'ll, T> = (
BackNodeWriteLock<'ll, T>, BackNodeWriteLock<'ll, T>,
RwLockWriteGuard<'ll, NodeInner<'ll, T>>, RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>,
Option<RwLockWriteGuard<'ll, NodeInner<'ll, T>>>, Option<RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>>,
); );
type WriteOnlyAroundTriplet<'ll, T> = ( type WriteOnlyAroundTriplet<'ll, T> = (
BackNodeWriteLock<'ll, T>, BackNodeWriteLock<'ll, T>,
Option<RwLockWriteGuard<'ll, NodeInner<'ll, T>>>, Option<RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>>,
); );
impl<'ll, T> Node<'ll, T> {
// TODO: think about the isolaed state of the following 3 fn's
impl<'ll, T> Node<'ll, T> {
/// Creates a new node in the heap, will link to `prev` and `next` but will be isolated, it can /// Creates a new node in the heap, will link to `prev` and `next` but will be isolated, it can
/// be thought of just ita data and the two pointers, having it isolated doesn't guarantee any /// be thought of just ita data and the two pointers, having it isolated doesn't guarantee any
/// integration into the linked list. /// integration into the linked list.
@@ -73,19 +65,10 @@ impl<'ll, T> Node<'ll, T> {
/// considered that the `prev` and `next` refs are being held. /// considered that the `prev` and `next` refs are being held.
#[must_use] #[must_use]
pub fn new(data: T, prev: NodeBackPtr<'ll, T>, next: Option<&'ll Node<'ll, T>>) -> Self { pub fn new(data: T, prev: NodeBackPtr<'ll, T>, next: Option<&'ll Node<'ll, T>>) -> Self {
Self(RwLock::new(NodeInner { prev, next, data })) Self {
} ptrs: RwLock::new(NodePtrPair { prev, next }),
data,
/// Boxes [`self`] }
#[must_use]
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
/// Leaks [`self`] as a [`Box<Self>`]
#[must_use]
pub fn leak(self: Box<Self>) -> &'static mut Self {
Box::leak(self)
} }
pub fn new_leaked( pub fn new_leaked(
@@ -96,6 +79,52 @@ impl<'ll, T> Node<'ll, T> {
Box::leak(Box::new(Self::new(data, prev, next))) Box::leak(Box::new(Self::new(data, prev, next)))
} }
/// Boxes [`self`]
#[must_use]
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}
impl<'ll, T: ?Sized> Node<'ll, T> {
/// # Panics
///
/// On arithmetic overflow
fn layout_for(data: &T) -> (Layout, usize) {
Layout::new::<RwLock<NodePtrPair<'ll, T>>>()
.extend(Layout::for_value(data))
.unwrap()
}
/// [`Self::new_leaked`] from a DST type
pub fn leaked_from_dst(
data: &T,
prev: NodeBackPtr<'ll, T>,
next: Option<&'ll Node<'ll, T>>,
) -> &'ll mut Self
where
T: dst_clone::DstClone,
{
let (layout, data_offset) = Self::layout_for(data);
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
handle_alloc_error(layout)
}
unsafe {
*(ptr.cast::<RwLock<NodePtrPair<'ll, T>>>()) = RwLock::new(NodePtrPair { prev, next });
data.copy(ptr.add(data_offset));
}
Box::leak(unsafe { Box::from_raw(ptr::from_raw_parts_mut(ptr, metadata(data))) })
}
/// Leaks [`self`] as a [`Box<Self>`]
#[must_use]
pub fn leak(self: Box<Self>) -> &'static mut Self {
Box::leak(self)
}
/// # Safety /// # Safety
/// ///
/// The [`self`] pointer must come from a [`Box`] allocation like [`Self::boxed`] and /// The [`self`] pointer must come from a [`Box`] allocation like [`Self::boxed`] and
@@ -111,8 +140,8 @@ impl<'ll, T> Node<'ll, T> {
/// There must be no references left to [`self`] /// There must be no references left to [`self`]
pub unsafe fn wait_free(&self) { pub unsafe fn wait_free(&self) {
loop { loop {
if self.is_locked() { if self.ptrs.is_locked() {
drop(self.write()); drop(self.ptrs.write());
} else { } else {
break; break;
} }
@@ -123,7 +152,7 @@ impl<'ll, T> Node<'ll, T> {
} }
pub fn lock_and_back(&'ll self) -> WriteAndBackDoublet<'ll, T> { pub fn lock_and_back(&'ll self) -> WriteAndBackDoublet<'ll, T> {
let mut self_read = self.upgradable_read(); let mut self_read = self.ptrs.upgradable_read();
// "soft" back lock // "soft" back lock
match self_read.prev.try_write() { match self_read.prev.try_write() {
Some(prev_write) => (prev_write, self_read), Some(prev_write) => (prev_write, self_read),
@@ -154,7 +183,7 @@ impl<'ll, T> Node<'ll, T> {
let (prev_write, self_read) = self.lock_and_back(); let (prev_write, self_read) = self.lock_and_back();
// Now `prev` is write locked and we can block forwards // Now `prev` is write locked and we can block forwards
let self_write = RwLockUpgradableReadGuard::upgrade(self_read); let self_write = RwLockUpgradableReadGuard::upgrade(self_read);
let next_write = self_write.next.map(|n| n.write()); let next_write = self_write.next.map(|n| n.ptrs.write());
(prev_write, self_write, next_write) (prev_write, self_write, next_write)
} }
@@ -164,7 +193,7 @@ impl<'ll, T> Node<'ll, T> {
/// The passed locks must also be consecutive for this to respect topology. /// The passed locks must also be consecutive for this to respect topology.
/// ///
/// This node will remain isolated. See [`topology_safety`]. /// This node will remain isolated. See [`topology_safety`].
pub unsafe fn isolate(self_read: &NodeInner<'ll, T>, locks: WriteOnlyAroundTriplet<'ll, T>) { pub unsafe fn isolate(self_read: &NodePtrPair<'ll, T>, locks: WriteOnlyAroundTriplet<'ll, T>) {
let (mut back_write, next_write) = locks; let (mut back_write, next_write) = locks;
back_write.set_next(self_read.next); back_write.set_next(self_read.next);
@@ -209,19 +238,19 @@ impl<'ll, T> Node<'ll, T> {
} }
/// Generic Write Lock of a [`NodeBackPtr`] /// Generic Write Lock of a [`NodeBackPtr`]
pub enum BackNodeWriteLock<'ll, T> { pub enum BackNodeWriteLock<'ll, T: ?Sized> {
Head(RwLockWriteGuard<'ll, NodeHeadInner<'ll, T>>), Head(RwLockWriteGuard<'ll, NodeHeadInner<'ll, T>>),
Node(RwLockWriteGuard<'ll, NodeInner<'ll, T>>), Node(RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>),
} }
/// Generic Read Lock of a [`NodeBackPtr`] /// Generic Read Lock of a [`NodeBackPtr`]
pub enum BackNodeReadLock<'ll, T> { pub enum BackNodeReadLock<'ll, T: ?Sized> {
Head(RwLockReadGuard<'ll, NodeHeadInner<'ll, T>>), Head(RwLockReadGuard<'ll, NodeHeadInner<'ll, T>>),
Node(RwLockReadGuard<'ll, NodeInner<'ll, T>>), Node(RwLockReadGuard<'ll, NodePtrPair<'ll, T>>),
} }
#[allow(clippy::enum_glob_use)] #[allow(clippy::enum_glob_use)]
impl<'ll, T> NodeBackPtr<'ll, T> { impl<'ll, T: ?Sized> NodeBackPtr<'ll, T> {
#[must_use] #[must_use]
pub fn ptr_eq(&self, other: &Self) -> bool { pub fn ptr_eq(&self, other: &Self) -> bool {
use NodeBackPtr::*; use NodeBackPtr::*;
@@ -250,7 +279,7 @@ impl<'ll, T> NodeBackPtr<'ll, T> {
match self { match self {
Head(h) => WL::Head(h.write()), Head(h) => WL::Head(h.write()),
Node(n) => WL::Node(n.write()), Node(n) => WL::Node(n.ptrs.write()),
} }
} }
@@ -262,7 +291,7 @@ impl<'ll, T> NodeBackPtr<'ll, T> {
match self { match self {
Head(h) => RL::Head(h.read()), Head(h) => RL::Head(h.read()),
Node(n) => RL::Node(n.read()), Node(n) => RL::Node(n.ptrs.read()),
} }
} }
@@ -274,7 +303,7 @@ impl<'ll, T> NodeBackPtr<'ll, T> {
Some(match self { Some(match self {
Head(h) => WL::Head(h.try_write()?), Head(h) => WL::Head(h.try_write()?),
Node(n) => WL::Node(n.try_write()?), Node(n) => WL::Node(n.ptrs.try_write()?),
}) })
} }
@@ -286,12 +315,12 @@ impl<'ll, T> NodeBackPtr<'ll, T> {
Some(match self { Some(match self {
Head(h) => RL::Head(h.try_read()?), Head(h) => RL::Head(h.try_read()?),
Node(n) => RL::Node(n.try_read()?), Node(n) => RL::Node(n.ptrs.try_read()?),
}) })
} }
} }
impl<'ll, T> BackNodeWriteLock<'ll, T> { impl<'ll, T: ?Sized> BackNodeWriteLock<'ll, T> {
#[allow(clippy::enum_glob_use)] #[allow(clippy::enum_glob_use)]
fn set_next(&mut self, next: Option<&'ll Node<'ll, T>>) { fn set_next(&mut self, next: Option<&'ll Node<'ll, T>>) {
use BackNodeWriteLock::*; use BackNodeWriteLock::*;
@@ -303,4 +332,4 @@ impl<'ll, T> BackNodeWriteLock<'ll, T> {
} }
} }
impl<T> NodeInner<'_, T> {} impl<T> Node<'_, T> {}

View File

@@ -2,6 +2,7 @@
use std::{ use std::{
fmt::Debug, fmt::Debug,
ptr,
sync::{ sync::{
Barrier, Barrier,
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
@@ -11,31 +12,31 @@ use std::{
use super::*; use super::*;
static DROP_C: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone)]
struct StringWithDrop(String);
impl From<String> for StringWithDrop {
fn from(value: String) -> Self {
Self(value)
}
}
impl Drop for StringWithDrop {
fn drop(&mut self) {
DROP_C.fetch_add(1, Ordering::Relaxed);
}
}
impl Debug for StringWithDrop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<String as Debug>::fmt(&self.0, f)
}
}
#[test] #[test]
fn concurrency_and_scoped_drop() { fn concurrency_and_scoped_drop() {
static DROP_C: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone)]
struct StringWithDrop(String);
impl From<String> for StringWithDrop {
fn from(value: String) -> Self {
Self(value)
}
}
impl Drop for StringWithDrop {
fn drop(&mut self) {
DROP_C.fetch_add(1, Ordering::Relaxed);
}
}
impl Debug for StringWithDrop {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<String as Debug>::fmt(&self.0, f)
}
}
const CREATE1: usize = 100; const CREATE1: usize = 100;
const CREATE2: usize = 100; const CREATE2: usize = 100;
const NOT_POP: usize = 20; const NOT_POP: usize = 20;
@@ -79,3 +80,16 @@ fn concurrency_and_scoped_drop() {
assert_eq!(DROP_C.load(Ordering::Relaxed), CREATE1 + CREATE2); assert_eq!(DROP_C.load(Ordering::Relaxed), CREATE1 + CREATE2);
} }
} }
#[test]
fn unsized_store() {
create_ll!(LinkedList::<str>::new(), ll_val, ll);
let inserted = ll.prepend_dst("test");
let first_node = ll.0.read().start.expect("first node to be some");
assert!(ptr::addr_eq(inserted, first_node));
assert_eq!(&**inserted, "test");
del_ll!(ll_val, ll);
}

View File

@@ -1,5 +1,10 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![feature(arbitrary_self_types, arbitrary_self_types_pointers, decl_macro)] #![feature(
arbitrary_self_types,
arbitrary_self_types_pointers,
decl_macro,
ptr_metadata
)]
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#[cfg(doc)] #[cfg(doc)]