#![allow(clippy::undocumented_unsafe_blocks)]
#![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))]
#![cfg_attr(feature = "alloc", feature(allocator_api))]
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::sync::Arc;
use core::{
convert::Infallible,
marker::PhantomPinned,
mem::MaybeUninit,
pin::Pin,
ptr::{self, addr_of_mut},
};
use pinned_init::*;
#[cfg(feature = "std")]
use std::sync::Arc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
extern crate alloc;
#[allow(unused_attributes)]
#[path = "../examples/error.rs"]
mod error;
use error::Error;
#[pin_data(PinnedDrop)]
pub struct RingBuffer<T, const SIZE: usize> {
buffer: [MaybeUninit<T>; SIZE],
head: *mut T,
tail: *mut T,
#[pin]
_pin: PhantomPinned,
}
#[pinned_drop]
impl<T, const SIZE: usize> PinnedDrop for RingBuffer<T, SIZE> {
fn drop(self: Pin<&mut Self>) {
let this = unsafe { self.get_unchecked_mut() };
while !ptr::eq(this.tail, this.head) {
unsafe { this.tail.drop_in_place() };
this.tail = unsafe { this.advance(this.tail) };
}
}
}
impl<T, const SIZE: usize> RingBuffer<T, SIZE> {
pub fn new() -> impl PinInit<Self> {
assert!(SIZE > 0);
pin_init!(&this in Self {
buffer <- unsafe { init_from_closure(|_| Ok::<_, Infallible>(())) },
head: unsafe { addr_of_mut!((*this.as_ptr()).buffer).cast::<T>() },
tail: unsafe { addr_of_mut!((*this.as_ptr()).buffer).cast::<T>() },
_pin: PhantomPinned,
})
}
pub fn push(self: Pin<&mut Self>, value: impl Init<T>) -> bool {
match self.try_push(value) {
Ok(res) => res,
Err(i) => match i {},
}
}
pub fn try_push<E>(self: Pin<&mut Self>, value: impl Init<T, E>) -> Result<bool, E> {
let this = unsafe { self.get_unchecked_mut() };
let next_head = unsafe { this.advance(this.head) };
if ptr::eq(next_head, this.tail) {
return Ok(false);
}
unsafe { value.__init(this.head)? };
this.head = next_head;
Ok(true)
}
pub fn pop(self: Pin<&mut Self>) -> Option<T> {
let this = unsafe { self.get_unchecked_mut() };
if ptr::eq(this.head, this.tail) {
return None;
}
let value = unsafe { this.tail.read() };
this.tail = unsafe { this.advance(this.tail) };
Some(value)
}
pub fn pop_no_stack(self: Pin<&mut Self>) -> Option<impl Init<T> + '_> {
let this = unsafe { self.get_unchecked_mut() };
if ptr::eq(this.head, this.tail) {
return None;
}
let remove_init = |slot| {
unsafe { ptr::copy_nonoverlapping(this.tail, slot, 1) };
this.tail = unsafe { this.advance(this.tail) };
Ok(())
};
Some(unsafe { init_from_closure(remove_init) })
}
unsafe fn advance(&mut self, ptr: *mut T) -> *mut T {
let ptr = unsafe { ptr.add(1) };
let origin: *mut _ = addr_of_mut!(self.buffer);
let origin = origin.cast::<T>();
let offset = unsafe { ptr.offset_from(origin) };
if offset >= SIZE as isize {
origin
} else {
ptr
}
}
}
#[test]
fn on_stack() -> Result<(), Infallible> {
stack_pin_init!(let buf = RingBuffer::<u8, 64>::new());
if let Some(elem) = buf.as_mut().pop() {
panic!("found in empty buffer!: {elem}");
}
assert!(buf.as_mut().push(10));
assert!(buf.as_mut().push(42));
assert_eq!(buf.as_mut().pop(), Some(10));
assert_eq!(buf.as_mut().pop(), Some(42));
assert_eq!(buf.as_mut().pop(), None);
assert!(buf.as_mut().push(42));
assert!(buf.as_mut().push(24));
assert_eq!(buf.as_mut().pop(), Some(42));
assert!(buf.as_mut().push(25));
assert_eq!(buf.as_mut().pop(), Some(24));
assert_eq!(buf.as_mut().pop(), Some(25));
assert_eq!(buf.as_mut().pop(), None);
for i in 0..63 {
assert!(buf.as_mut().push(i));
}
assert!(!buf.as_mut().push(42));
for i in 0..63 {
if let Some(value) = buf.as_mut().pop_no_stack() {
stack_pin_init!(let value = value);
assert_eq!(*value, i);
} else {
panic!("Expected more values");
}
}
assert_eq!(buf.as_mut().pop(), None);
Ok(())
}
#[derive(PartialEq, Eq, Debug)]
pub struct EvenU64 {
info: String,
data: u64,
}
impl EvenU64 {
#[allow(clippy::manual_is_multiple_of)]
pub fn new2(value: u64) -> impl Init<Self, Error> {
try_init!(Self {
info: "Hello world!".to_owned(),
data: if value % 2 == 0 {
value
} else {
return Err(Error);
},
}? Error)
}
#[allow(clippy::manual_is_multiple_of)]
pub fn new(value: u64) -> impl Init<Self, ()> {
try_init!(Self {
info: "Hello world!".to_owned(),
data: if value % 2 == 0 {
value
} else {
return Err(());
},
}?())
}
}
#[test]
fn even_stack() {
stack_try_pin_init!(let val = EvenU64::new(0));
assert_eq!(
val.as_deref_mut(),
Ok(&mut EvenU64 {
info: "Hello world!".to_owned(),
data: 0
})
);
stack_try_pin_init!(let val = EvenU64::new(1));
assert_eq!(val, Err(()));
}
#[test]
#[cfg(any(feature = "std", feature = "alloc"))]
fn even_failing() {
assert!(matches!(Box::try_pin_init(EvenU64::new2(3)), Err(Error)));
assert!(matches!(Box::try_init(EvenU64::new2(3)), Err(Error)));
assert!(matches!(Arc::try_pin_init(EvenU64::new2(5)), Err(Error)));
assert!(matches!(Box::try_init(EvenU64::new2(3)), Err(Error)));
assert!(matches!(Arc::try_init(EvenU64::new2(5)), Err(Error)));
}
#[test]
#[cfg(any(feature = "std", feature = "alloc"))]
fn with_failing_inner() {
let mut buf = Box::pin_init(RingBuffer::<EvenU64, 4>::new()).unwrap();
assert_eq!(buf.as_mut().try_push(EvenU64::new(0)), Ok(true));
assert_eq!(buf.as_mut().try_push(EvenU64::new(1)), Err(()));
assert_eq!(buf.as_mut().try_push(EvenU64::new(2)), Ok(true));
assert_eq!(buf.as_mut().try_push(EvenU64::new(3)), Err(()));
assert_eq!(buf.as_mut().try_push(EvenU64::new(4)), Ok(true));
assert_eq!(buf.as_mut().try_push(EvenU64::new(5)), Ok(false));
assert_eq!(buf.as_mut().try_push(EvenU64::new(6)), Ok(false));
assert_eq!(
buf.as_mut().pop(),
Some(EvenU64 {
info: "Hello world!".to_owned(),
data: 0
})
);
assert_eq!(
buf.as_mut().pop(),
Some(EvenU64 {
info: "Hello world!".to_owned(),
data: 2
})
);
assert_eq!(
buf.as_mut().pop(),
Some(EvenU64 {
info: "Hello world!".to_owned(),
data: 4
})
);
assert_eq!(buf.as_mut().pop(), None);
}
#[allow(dead_code)]
#[derive(Debug)]
struct BigStruct {
buf: [u8; 1024 * 1024],
oth: MaybeUninit<u8>,
}
#[test]
#[cfg(any(feature = "std", feature = "alloc"))]
#[cfg_attr(miri, ignore)]
fn big_struct() {
let x = Arc::init(init!(BigStruct {
buf <- init_zeroed(),
oth <- init_zeroed(),
}));
println!("{x:?}");
let x = Box::init(init!(BigStruct {
buf <- init_zeroed(),
oth <- init_zeroed(),
}));
println!("{x:?}");
}
#[test]
#[cfg(any(feature = "std", feature = "alloc"))]
#[cfg_attr(miri, ignore)]
fn with_big_struct() {
#[allow(unused_attributes)]
#[path = "../examples/mutex.rs"]
mod mutex;
use mutex::*;
let buf = Arc::pin_init(CMutex::new(RingBuffer::<BigStruct, 64>::new())).unwrap();
let mut buf = buf.lock();
for _ in 0..63 {
assert_eq!(
buf.as_mut().try_push(init!(BigStruct{
buf <- init_zeroed(),
oth <- uninit::<_, Infallible>(),
})),
Ok(true)
);
}
assert_eq!(
buf.as_mut().try_push(init!(BigStruct{
buf <- init_zeroed(),
oth <- uninit::<_, Infallible>(),
})),
Ok(false)
);
for _ in 0..63 {
assert!(buf.as_mut().pop_no_stack().is_some());
}
}
#[test]
#[cfg(feature = "alloc")]
#[cfg_attr(any(miri, NO_ALLOC_FAIL_TESTS, target_os = "macos"), ignore)]
fn too_big_pinned() {
use core::alloc::AllocError;
assert!(matches!(
Box::pin_init(RingBuffer::<u8, { 1024 * 1024 * 1024 * 1024 }>::new()),
Err(AllocError)
));
assert!(matches!(
Arc::pin_init(RingBuffer::<u8, { 1024 * 1024 * 1024 * 1024 }>::new()),
Err(AllocError)
));
}