feat: unsafely support ?Sized
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -24,9 +24,15 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
name = "concurrent-linked-list"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dst-clone",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dst-clone"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.javalsai.tuxcord.net/tuxcord/dst-clone#6a32bfa59455adafde07bafd3bc9f2182cd5127b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
@@ -68,9 +74,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.13"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
|
||||
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@@ -4,4 +4,5 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dst-clone = { git = "https://git.javalsai.tuxcord.net/tuxcord/dst-clone", version = "0" }
|
||||
parking_lot = { version = "0", default-features = false }
|
||||
|
@@ -6,7 +6,7 @@ use std::{marker::PhantomPinned, mem::transmute, ops::Deref, pin::Pin};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::double::node::{BackNodeWriteLock, Node, NodeBackPtr};
|
||||
use crate::double::node::{BackNodeWriteLock, Node};
|
||||
|
||||
pub mod node;
|
||||
|
||||
@@ -16,26 +16,26 @@ pub mod node;
|
||||
// case the task holding the left lock is moving rightwards.
|
||||
// Rightwards locking can be blocking.
|
||||
|
||||
pub struct NodeHeadInner<'ll, T> {
|
||||
pub struct NodeHeadInner<'ll, T: ?Sized> {
|
||||
start: Option<&'ll node::Node<'ll, T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for NodeHeadInner<'_, T> {
|
||||
impl<T: ?Sized> Default for NodeHeadInner<'_, T> {
|
||||
fn default() -> Self {
|
||||
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]
|
||||
fn default() -> Self {
|
||||
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>>;
|
||||
|
||||
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]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@@ -85,13 +85,30 @@ impl<'ll, T> NodeHead<'ll, T> {
|
||||
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 next = self_lock.start;
|
||||
let next_lock = next.map(|n| n.write());
|
||||
let new_node = Node::new_leaked(data, NodeBackPtr::new_head(self), next);
|
||||
let next_lock = next.map(|n| n.ptrs.write());
|
||||
let new_node = Node::new_leaked(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
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -102,8 +119,8 @@ impl<'ll, T> NodeHead<'ll, T> {
|
||||
pub unsafe fn pop(&'ll self) -> Option<()> {
|
||||
let self_lock = self.write();
|
||||
let pop_node = self_lock.start?;
|
||||
let pop_node_lock = pop_node.write();
|
||||
let next_node_lock = pop_node_lock.next.map(|n| n.write());
|
||||
let pop_node_lock = pop_node.ptrs.write();
|
||||
let next_node_lock = pop_node_lock.next.map(|n| n.ptrs.write());
|
||||
|
||||
// SAFETY: locked all along and consecutive nodes
|
||||
unsafe {
|
||||
@@ -115,6 +132,7 @@ impl<'ll, T> NodeHead<'ll, T> {
|
||||
|
||||
drop(pop_node_lock);
|
||||
// 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() }
|
||||
|
||||
Some(())
|
||||
@@ -127,8 +145,8 @@ impl<'ll, T> NodeHead<'ll, T> {
|
||||
let mut total = Vec::new();
|
||||
let mut next_node = self.read().start;
|
||||
while let Some(node) = next_node {
|
||||
let read = node.read();
|
||||
total.push(read.data.clone());
|
||||
let read = node.ptrs.read();
|
||||
total.push(node.data.clone());
|
||||
next_node = read.next;
|
||||
}
|
||||
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.
|
||||
///
|
||||
/// Please see [`create_ll`] and [`del_ll`]
|
||||
pub struct LinkedList<'ll, T> {
|
||||
pub struct LinkedList<'ll, T: ?Sized> {
|
||||
head: NodeHead<'ll, T>,
|
||||
_pinned: PhantomPinned,
|
||||
}
|
||||
|
||||
impl<'ll, T> LinkedList<'ll, T> {
|
||||
impl<'ll, T: ?Sized> LinkedList<'ll, T> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
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]
|
||||
fn default() -> 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) {
|
||||
// Extend to 'static so the compiler doesn't cry, we know this covers 'll
|
||||
let myself = unsafe { self.extend_for(&()) };
|
||||
@@ -227,8 +245,8 @@ impl<T> Drop for LinkedList<'_, T> {
|
||||
/// ll.prepend("test2".to_string());
|
||||
/// ```
|
||||
pub macro create_ll($rhs:expr, $val:ident, $ref:ident) {
|
||||
let $val = $rhs;
|
||||
let scope = ();
|
||||
let $val = $rhs;
|
||||
let $ref = unsafe { $val.extend_for(&scope) };
|
||||
}
|
||||
|
||||
|
@@ -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 super::NodeHeadInner;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Node<'ll, T>(RwLock<NodeInner<'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>),
|
||||
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
|
||||
pub mod topology_safety;
|
||||
|
||||
// TODO: RwLock the ptrs only instead of the node
|
||||
// Box<(RwLock<(&prev, &next)>, T)>
|
||||
// instead of
|
||||
// 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
|
||||
pub struct NodeInner<'ll, T> {
|
||||
pub(crate) prev: NodeBackPtr<'ll, T>,
|
||||
pub(crate) next: Option<&'ll Node<'ll, T>>,
|
||||
pub struct NodePtrPair<'ll, T: ?Sized> {
|
||||
pub prev: NodeBackPtr<'ll, T>,
|
||||
pub next: Option<&'ll Node<'ll, T>>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Node<'ll, T: ?Sized> {
|
||||
pub ptrs: RwLock<NodePtrPair<'ll, T>>,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
impl<'ll, T> Deref for Node<'ll, T> {
|
||||
type Target = RwLock<NodeInner<'ll, T>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for NodeInner<'_, T> {
|
||||
impl<T: ?Sized> Deref for Node<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -43,28 +36,27 @@ impl<T> Deref for NodeInner<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for NodeBackPtr<'_, T> {}
|
||||
impl<T> Clone for NodeBackPtr<'_, T> {
|
||||
impl<T: ?Sized> Copy for NodeBackPtr<'_, T> {}
|
||||
impl<T: ?Sized> Clone for NodeBackPtr<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
type WriteAndBackDoublet<'ll, T> = (
|
||||
BackNodeWriteLock<'ll, T>,
|
||||
RwLockUpgradableReadGuard<'ll, NodeInner<'ll, T>>,
|
||||
RwLockUpgradableReadGuard<'ll, NodePtrPair<'ll, T>>,
|
||||
);
|
||||
type WriteSurroundTriplet<'ll, T> = (
|
||||
BackNodeWriteLock<'ll, T>,
|
||||
RwLockWriteGuard<'ll, NodeInner<'ll, T>>,
|
||||
Option<RwLockWriteGuard<'ll, NodeInner<'ll, T>>>,
|
||||
RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>,
|
||||
Option<RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>>,
|
||||
);
|
||||
type WriteOnlyAroundTriplet<'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
|
||||
/// be thought of just ita data and the two pointers, having it isolated doesn't guarantee any
|
||||
/// 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.
|
||||
#[must_use]
|
||||
pub fn new(data: T, prev: NodeBackPtr<'ll, T>, next: Option<&'ll Node<'ll, T>>) -> Self {
|
||||
Self(RwLock::new(NodeInner { 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)
|
||||
Self {
|
||||
ptrs: RwLock::new(NodePtrPair { prev, next }),
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_leaked(
|
||||
@@ -96,6 +79,52 @@ impl<'ll, T> Node<'ll, T> {
|
||||
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
|
||||
///
|
||||
/// 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`]
|
||||
pub unsafe fn wait_free(&self) {
|
||||
loop {
|
||||
if self.is_locked() {
|
||||
drop(self.write());
|
||||
if self.ptrs.is_locked() {
|
||||
drop(self.ptrs.write());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -123,7 +152,7 @@ impl<'ll, T> Node<'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
|
||||
match self_read.prev.try_write() {
|
||||
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();
|
||||
// Now `prev` is write locked and we can block forwards
|
||||
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)
|
||||
}
|
||||
@@ -164,7 +193,7 @@ impl<'ll, T> Node<'ll, T> {
|
||||
/// The passed locks must also be consecutive for this to respect topology.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
back_write.set_next(self_read.next);
|
||||
@@ -209,19 +238,19 @@ impl<'ll, T> Node<'ll, T> {
|
||||
}
|
||||
|
||||
/// Generic Write Lock of a [`NodeBackPtr`]
|
||||
pub enum BackNodeWriteLock<'ll, T> {
|
||||
pub enum BackNodeWriteLock<'ll, T: ?Sized> {
|
||||
Head(RwLockWriteGuard<'ll, NodeHeadInner<'ll, T>>),
|
||||
Node(RwLockWriteGuard<'ll, NodeInner<'ll, T>>),
|
||||
Node(RwLockWriteGuard<'ll, NodePtrPair<'ll, T>>),
|
||||
}
|
||||
|
||||
/// Generic Read Lock of a [`NodeBackPtr`]
|
||||
pub enum BackNodeReadLock<'ll, T> {
|
||||
pub enum BackNodeReadLock<'ll, T: ?Sized> {
|
||||
Head(RwLockReadGuard<'ll, NodeHeadInner<'ll, T>>),
|
||||
Node(RwLockReadGuard<'ll, NodeInner<'ll, T>>),
|
||||
Node(RwLockReadGuard<'ll, NodePtrPair<'ll, T>>),
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
impl<'ll, T> NodeBackPtr<'ll, T> {
|
||||
impl<'ll, T: ?Sized> NodeBackPtr<'ll, T> {
|
||||
#[must_use]
|
||||
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||
use NodeBackPtr::*;
|
||||
@@ -250,7 +279,7 @@ impl<'ll, T> NodeBackPtr<'ll, T> {
|
||||
|
||||
match self {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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)]
|
||||
fn set_next(&mut self, next: Option<&'ll Node<'ll, T>>) {
|
||||
use BackNodeWriteLock::*;
|
||||
@@ -303,4 +332,4 @@ impl<'ll, T> BackNodeWriteLock<'ll, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NodeInner<'_, T> {}
|
||||
impl<T> Node<'_, T> {}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
ptr,
|
||||
sync::{
|
||||
Barrier,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -11,31 +12,31 @@ use std::{
|
||||
|
||||
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]
|
||||
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 CREATE2: usize = 100;
|
||||
const NOT_POP: usize = 20;
|
||||
@@ -79,3 +80,16 @@ fn concurrency_and_scoped_drop() {
|
||||
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);
|
||||
}
|
||||
|
@@ -1,5 +1,10 @@
|
||||
#![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)]
|
||||
|
||||
#[cfg(doc)]
|
||||
|
Reference in New Issue
Block a user