#![doc = include_str!("../README.md")]
#![cfg_attr(not(any(doc, test)), no_std)]
#[cfg(doc)]
extern crate alloc;
use core::any::Any;
use core::cell::Cell;
use core::ffi::CStr;
use core::marker::PhantomData;
#[repr(transparent)]
pub struct NonDeDuplicatedFlexible<OWN: Any + Send + Sync, TO: Any + Send + Sync + ?Sized> {
cell: Cell<OWN>,
_t: PhantomData<TO>,
}
#[allow(type_alias_bounds)]
pub type NonDeDuplicated<T: Any + Send + Sync> = NonDeDuplicatedFlexible<T, T>;
impl<T: Any + Send + Sync> NonDeDuplicated<T> {
pub const fn new(value: T) -> Self {
Self {
cell: Cell::new(value),
_t: PhantomData,
}
}
pub const fn get(&self) -> &T {
let ptr = self.cell.as_ptr();
unsafe { &*ptr }
}
}
const fn copy_bytes_to_array(to: &mut [u8], from: &[u8], len: usize) {
if from.len() > len {
let msg = match from.len() - len {
1 => "Target length is 1 byte too small.",
2 => "Target length is 2 bytes too small.",
3 => "Target length is 3 bytes too small.",
4 => "Target length is 4 bytes too small.",
_ => "Target length is more than 4 bytes too small.",
};
panic!("{}", msg)
}
if from.len() < len {
let msg = match len - from.len() {
1 => "Target length is 1 byte too large.",
2 => "Target length is 2 bytes too large.",
3 => "Target length is 3 bytes too large.",
4 => "Target length is 4 bytes too large.",
_ => "Target length is more than 4 bytes too large.",
};
panic!("{}", msg)
}
if to.len() != len {
panic!("Target slice length differs to the specified length.")
}
let mut i = 0;
while i < len {
to[i] = from[i];
i += 1;
}
}
const fn bytes_to_array<const N: usize>(bytes: &[u8]) -> [u8; N] {
let mut arr = [0u8; N];
copy_bytes_to_array(&mut arr, bytes, N);
arr
}
pub type NonDeDuplicatedStr<const N: usize> = NonDeDuplicatedFlexible<[u8; N], str>;
impl<const N: usize> NonDeDuplicatedStr<N> {
pub const fn new(s: &str) -> Self {
Self {
cell: Cell::new(bytes_to_array(s.as_bytes())),
_t: PhantomData,
}
}
pub const fn get(&self) -> &str {
let ptr = self.cell.as_ptr();
let bytes = unsafe { &*ptr };
match core::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => unreachable!(),
}
}
}
pub type NonDeDuplicatedCStr<const N: usize> = NonDeDuplicatedFlexible<[u8; N], CStr>;
impl<const N: usize> NonDeDuplicatedCStr<N> {
pub const fn new(s: &CStr) -> Self {
Self {
cell: Cell::new(bytes_to_array(s.to_bytes())),
_t: PhantomData,
}
}
pub const fn new_from_bytes(arr: [u8; N]) -> Self {
let _ = core::hint::black_box(CStr::from_bytes_with_nul(&arr));
Self {
cell: Cell::new(arr),
_t: PhantomData,
}
}
pub const fn new_from_str(s: &str) -> Self {
let mut arr = [0u8; N];
if let Some((_, sub_slice)) = arr.split_last_mut() {
crate::copy_bytes_to_array(sub_slice, s.as_bytes(), s.len());
} else {
unreachable!()
}
Self::new_from_bytes(arr)
}
pub const fn get(&self) -> &CStr {
let ptr = self.cell.as_ptr();
let bytes = unsafe { &*ptr };
match CStr::from_bytes_with_nul(bytes) {
Ok(s) => s,
Err(_) => unreachable!(),
}
}
}
unsafe impl<OWN: Any + Send + Sync, TO: Any + Send + Sync + ?Sized> Sync
for NonDeDuplicatedFlexible<OWN, TO>
{
}
impl<OWN: Any + Send + Sync, TO: Any + Send + Sync + ?Sized> Drop
for NonDeDuplicatedFlexible<OWN, TO>
{
fn drop(&mut self) {
#[cfg(any(debug_assertions, miri))]
panic!("Do not use for local variables, const, or on heap. Use for static variables only.")
}
}
#[cfg(test)]
mod tests_shared {
pub const STR_CONST_FROM_BYTE_ARRAY_HI: &str = {
match str::from_utf8(&[b'H', b'i']) {
Ok(s) => s,
Err(_) => unreachable!(),
}
};
pub const STR_CONST_FROM_BYTE_STRING_HELLO: &str = {
match str::from_utf8(b"Hello") {
Ok(s) => s,
Err(_) => unreachable!(),
}
};
}
#[cfg(test)]
mod tests_without_ndd {
use crate::tests_shared::{STR_CONST_FROM_BYTE_ARRAY_HI, STR_CONST_FROM_BYTE_STRING_HELLO};
use core::ptr;
const U8_CONST: u8 = b'A';
static U8_STATIC_1: u8 = b'A';
static U8_STATIC_2: u8 = b'A';
#[test]
fn addresses_unique_between_statics() {
assert!(!ptr::eq(&U8_STATIC_1, &U8_STATIC_2));
}
#[cfg(not(any(debug_assertions, miri)))]
#[should_panic(expected = "assertion failed: !ptr::eq(&U8_STATIC_1, &U8_CONST)")]
#[test]
fn u8_global_const_and_global_static_release() {
assert!(!ptr::eq(&U8_STATIC_1, &U8_CONST));
}
#[cfg(any(debug_assertions, miri))]
#[test]
fn u8_global_const_global_and_static_debug_and_miri() {
assert!(!ptr::eq(&U8_STATIC_1, &U8_CONST));
}
#[cfg(not(miri))]
#[test]
#[should_panic(expected = "assertion failed: !ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, \"Hi\")")]
fn str_global_byte_slice_const_and_local_str_release_and_debug() {
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, "Hi"));
}
#[cfg(miri)]
#[test]
fn str_global_byte_slice_const_and_local_str_miri() {
assert!(!ptr::eq(STR_CONST_FROM_BYTE_ARRAY_HI, "Hi"));
}
#[test]
fn str_global_byte_by_byte_const_and_local_static() {
assert!(ptr::eq(STR_CONST_FROM_BYTE_STRING_HELLO, "Hello"));
}
static STR_STATIC: &str = "Ciao";
#[cfg(not(miri))]
#[should_panic(expected = "assertion failed: !ptr::eq(local_const_based_slice, STR_STATIC)")]
#[test]
fn str_local_const_based_and_global_static_release_and_debug() {
str_local_const_based_and_global_static_impl();
}
#[cfg(miri)]
#[test]
fn str_local_const_based_and_global_static_miri() {
str_local_const_based_and_global_static_impl();
}
fn str_local_const_based_and_global_static_impl() {
const LOCAL_CONST_ARR: [u8; 4] = [b'C', b'i', b'a', b'o'];
let local_const_based_slice: &str = str::from_utf8(&LOCAL_CONST_ARR).unwrap();
assert!(!ptr::eq(local_const_based_slice, STR_STATIC));
}
mod cross_module_static {
pub static STATIC_OPT_U8_A: Option<u8> = Some(b'A');
}
mod cross_module_const {
use core::ptr;
pub const CONST_OPT_U8_A: Option<u8> = Some(b'A');
#[cfg(not(any(debug_assertions, miri)))]
#[test]
#[should_panic(
expected = "assertion failed: !ptr::eq(&CONST_OPT_U8_A, &super::cross_module_static::STATIC_OPT_U8_A)"
)]
fn option_u8_global_const_global_static_release() {
assert!(!ptr::eq(
&CONST_OPT_U8_A,
&super::cross_module_static::STATIC_OPT_U8_A
));
}
#[cfg(any(debug_assertions, miri))]
#[test]
fn option_u8_global_const_global_static_debug_and_miri() {
assert!(!ptr::eq(
&CONST_OPT_U8_A,
&super::cross_module_static::STATIC_OPT_U8_A
));
}
}
}
#[cfg(test)]
mod tests_with_ndd {
use super::*;
use crate::tests_shared::{STR_CONST_FROM_BYTE_ARRAY_HI, STR_CONST_FROM_BYTE_STRING_HELLO};
use core::ptr;
const U8_CONST: u8 = b'A';
static U8_STATIC_1: u8 = b'A';
static U8_STATIC_2: u8 = b'A';
#[allow(dead_code)]
const fn expect_sync_ref<T: Sync>() {}
#[allow(dead_code)]
const _: () = expect_sync_ref::<NonDeDuplicated<u8>>();
static U8_NDD: NonDeDuplicated<u8> = NonDeDuplicated::new(U8_CONST);
static U8_NDD_REF: &u8 = U8_NDD.get();
#[test]
fn u8_global_const_and_ndd() {
assert!(!ptr::eq(U8_NDD_REF, &U8_CONST));
assert!(!ptr::eq(U8_NDD_REF, &U8_STATIC_1));
assert!(!ptr::eq(U8_NDD_REF, &U8_STATIC_2));
}
static STR_NDD_HI: NonDeDuplicatedStr<5> = NonDeDuplicatedStr::new("Hello");
#[test]
fn str_ndd_hi() {
assert!(!ptr::eq(STR_NDD_HI.get(), "Hi"));
assert!(!ptr::eq(STR_NDD_HI.get(), STR_CONST_FROM_BYTE_ARRAY_HI));
assert!(!ptr::eq(STR_NDD_HI.get(), STR_CONST_FROM_BYTE_STRING_HELLO));
}
static STR_NDD_CIAO: NonDeDuplicatedStr<4> = NonDeDuplicatedStr::new("Ciao");
#[test]
fn str_local_const_based_and_str_ndd() {
const LOCAL_CONST_ARR: [u8; 4] = [b'C', b'i', b'a', b'o'];
let local_const_based_slice: &str = str::from_utf8(&LOCAL_CONST_ARR).unwrap();
assert!(!ptr::eq(local_const_based_slice, STR_NDD_CIAO.get()));
}
#[test]
#[cfg(any(debug_assertions, miri))]
#[should_panic(
expected = "Do not use for local variables, const, or on heap. Use for static variables only."
)]
fn drop_panics_in_debug_and_miri() {
let _: NonDeDuplicated<()> = NonDeDuplicated::new(());
}
#[cfg(not(any(debug_assertions, miri)))]
#[test]
fn drop_silent_in_release() {
let _: NonDeDuplicated<()> = NonDeDuplicated::new(());
}
}