use alloc::{
borrow::{Borrow, Cow, ToOwned},
boxed::Box,
string::{String, ToString},
};
use core::{
convert::{TryFrom, TryInto},
fmt,
marker::PhantomData,
num::{
NonZeroI128,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI8,
NonZeroIsize,
NonZeroU128,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU8,
NonZeroUsize,
},
};
#[derive(Eq, PartialEq, Debug)]
pub struct Idx<T: ?Sized> {
v: NonZeroUsize,
_phantom: PhantomData<T>,
}
impl<T: ?Sized> Clone for Idx<T> {
fn clone(&self) -> Self {
Self {
v: self.v,
_phantom: PhantomData,
}
}
}
impl<T: ?Sized> fmt::Display for Idx<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.v, f)
}
}
impl<T: ?Sized + NewIndex> Idx<T> {
#[must_use]
pub fn try_new(v: NonZeroUsize) -> Option<Self> {
if <T as NewIndex>::new_index_allowed(v) {
Some(unsafe { Self::new(v) })
} else {
None
}
}
#[must_use]
crate fn try_new_usize(v: usize) -> Option<Self> {
NonZeroUsize::new(v).and_then(Self::try_new)
}
}
impl<T: ?Sized> Idx<T> {
#[must_use]
pub const unsafe fn new(v: NonZeroUsize) -> Self {
Self {
v,
_phantom: PhantomData,
}
}
#[must_use]
pub const unsafe fn from_usize(v: usize) -> Option<Self> {
if let Some(v) = NonZeroUsize::new(v) {
Some(unsafe { Self::new(v) })
} else {
None
}
}
#[must_use]
pub const fn get(&self) -> NonZeroUsize {
self.v
}
#[must_use]
pub const unsafe fn cast<U: ?Sized>(self) -> Idx<U> {
unsafe { Idx::new(self.v) }
}
#[must_use]
crate const unsafe fn new_unchecked(v: usize) -> Self {
unsafe { Self::new(NonZeroUsize::new_unchecked(v)) }
}
}
impl<T: ?Sized> Idx<T> {
#[must_use]
pub fn cast_safe<U: Borrow<T>>(self) -> Idx<U> {
unsafe { Idx::new(self.v) }
}
}
impl<T: ?Sized + FromIndex> Idx<T> {
#[must_use]
pub fn into_value(self) -> T {
<T as FromIndex>::from_index(self)
}
}
pub unsafe trait NewIndex: FromIndex + IntoIndex {
#[must_use]
fn new_index_allowed(v: NonZeroUsize) -> bool;
}
pub trait IntoIndex {
#[allow(clippy::wrong_self_convention)]
#[must_use]
fn into_index(&self) -> Option<Idx<Self>>;
}
pub trait FromIndex: Sized + IntoIndex {
#[must_use]
fn from_index(idx: Idx<Self>) -> Self;
}
macro_rules! impl_test_index {
($($t:ty,)+) => {
$(
impl IntoIndex for $t {
fn into_index(&self) -> Option<Idx<Self>> {
unsafe { Idx::from_usize(usize::try_from(*self).ok()?) }
}
}
impl FromIndex for $t {
fn from_index(idx: Idx<Self>) -> Self {
idx.get().get() as $t
}
}
unsafe impl NewIndex for $t {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
<$t>::try_from(idx.get()).is_ok()
}
}
)+
}
}
impl_test_index! {
i8, u8,
i16, u16,
i32, u32,
i64, u64,
i128, u128,
isize, usize,
}
macro_rules! impl_test_index_nonzero {
($($t:ty, $b:ty,)+) => {
$(
impl IntoIndex for $t {
fn into_index(&self) -> Option<Idx<Self>> {
Some(unsafe { Idx::new((*self).try_into().ok()?) })
}
}
impl FromIndex for $t {
fn from_index(idx: Idx<Self>) -> Self {
unsafe { Self::new_unchecked(idx.get().get() as _) }
}
}
unsafe impl NewIndex for $t {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
<$b>::try_from(idx.get()).is_ok()
}
}
)+
}
}
impl_test_index_nonzero! {
NonZeroI8, i8, NonZeroU8, u8,
NonZeroI16, i16, NonZeroU16, u16,
NonZeroI32, i32, NonZeroU32, u32,
NonZeroI64, i64, NonZeroU64, u64,
NonZeroI128, i128, NonZeroU128, u128,
NonZeroIsize, isize,
}
impl IntoIndex for NonZeroUsize {
fn into_index(&self) -> Option<Idx<Self>> {
Some(unsafe { Idx::new(*self) })
}
}
impl FromIndex for NonZeroUsize {
fn from_index(idx: Idx<Self>) -> Self {
idx.get()
}
}
unsafe impl NewIndex for NonZeroUsize {
fn new_index_allowed(_: NonZeroUsize) -> bool {
true
}
}
impl IntoIndex for char {
fn into_index(&self) -> Option<Idx<Self>> {
Some(unsafe { u32::from(*self).into_index()?.cast() })
}
}
impl FromIndex for char {
fn from_index(idx: Idx<Self>) -> Self {
unsafe { Self::from_u32_unchecked(idx.get().get() as u32) }
}
}
unsafe impl NewIndex for char {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
u32::try_from(idx.get())
.ok()
.and_then(Self::from_u32)
.is_some()
}
}
impl<T: IntoIndex> IntoIndex for &T {
fn into_index(&self) -> Option<Idx<Self>> {
Some(T::into_index(self)?.cast_safe())
}
}
impl<T: IntoIndex> IntoIndex for Box<T> {
fn into_index(&self) -> Option<Idx<Self>> {
Some(T::into_index(self)?.cast_safe())
}
}
impl<T: FromIndex> FromIndex for Box<T> {
fn from_index(idx: Idx<Self>) -> Self {
Self::new(T::from_index(unsafe { idx.cast() }))
}
}
unsafe impl<T: NewIndex> NewIndex for Box<T> {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
T::new_index_allowed(idx)
}
}
impl<T: IntoIndex> IntoIndex for Option<T> {
fn into_index(&self) -> Option<Idx<Self>> {
Some(unsafe { self.as_ref().and_then(T::into_index)?.cast() })
}
}
impl<T: FromIndex> FromIndex for Option<T> {
fn from_index(idx: Idx<Self>) -> Self {
Some(T::from_index(unsafe { idx.cast() }))
}
}
unsafe impl<T: NewIndex> NewIndex for Option<T> {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
T::new_index_allowed(idx)
}
}
impl<T: IntoIndex, E> IntoIndex for Result<T, E> {
fn into_index(&self) -> Option<Idx<Self>> {
Some(unsafe { self.as_ref().ok()?.into_index()?.cast() })
}
}
impl<T: FromIndex, E> FromIndex for Result<T, E> {
fn from_index(idx: Idx<Self>) -> Self {
Ok(T::from_index(unsafe { idx.cast() }))
}
}
unsafe impl<T: NewIndex, E> NewIndex for Result<T, E> {
fn new_index_allowed(idx: NonZeroUsize) -> bool {
T::new_index_allowed(idx)
}
}
impl<T: ?Sized + IntoIndex + ToOwned> IntoIndex for Cow<'_, T> {
fn into_index(&self) -> Option<Idx<Self>> {
Some(T::into_index(self)?.cast_safe())
}
}
impl<T: ?Sized + IntoIndex + ToOwned> FromIndex for Cow<'static, T>
where
<T as ToOwned>::Owned: FromIndex,
{
fn from_index(idx: Idx<Self>) -> Self {
Self::Owned(FromIndex::from_index(unsafe { idx.cast() }))
}
}
unsafe impl<T: ?Sized + IntoIndex + ToOwned> NewIndex for Cow<'static, T>
where
<T as ToOwned>::Owned: NewIndex,
{
fn new_index_allowed(idx: NonZeroUsize) -> bool {
<T as ToOwned>::Owned::new_index_allowed(idx)
}
}
impl IntoIndex for str {
fn into_index(&self) -> Option<Idx<Self>> {
if self.starts_with('0') {
None
} else {
let u = self.bytes().try_fold(0, |acc, c| -> Option<usize> {
acc.checked_mul(10)?
.checked_add(matches!(c, b'0'..=b'9').then(|| c - b'0')? as usize)
})?;
unsafe { Idx::from_usize(u) }
}
}
}
impl IntoIndex for String {
fn into_index(&self) -> Option<Idx<Self>> {
Some((**self).into_index()?.cast_safe())
}
}
impl FromIndex for String {
fn from_index(idx: Idx<Self>) -> Self {
idx.to_string()
}
}
unsafe impl NewIndex for String {
fn new_index_allowed(_: NonZeroUsize) -> bool {
true
}
}