#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![warn(clippy::pedantic)]
#![allow(clippy::must_use_candidate)]
#![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[macro_use]
mod macros;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::any::{Any, TypeId};
use core::fmt;
use core::iter::FusedIterator;
use core::mem;
use core::ops;
use core::slice;
use paste::paste;
#[cfg(feature = "derive")]
pub use field_access_derive::FieldAccess;
pub trait AnyFieldAccess: Any {
fn field_as_any(&self, field: &str) -> Option<&dyn Any>;
fn field_as_any_mut(&mut self, field: &str) -> Option<&mut dyn Any>;
fn field_names(&self) -> &'static [&'static str];
}
pub trait FieldAccess: AnyFieldAccess {
#[inline]
fn field(&self, field: &str) -> Option<Field<'_>> {
self.field_as_any(field).map(Field::new)
}
#[inline]
fn field_mut(&mut self, field: &str) -> Option<FieldMut<'_>> {
self.field_as_any_mut(field).map(FieldMut::new)
}
#[inline]
fn fields(&self) -> Fields<'_>
where
Self: Sized,
{
Fields::new(self)
}
}
impl<T> FieldAccess for T where T: AnyFieldAccess {}
#[derive(Debug, Clone)]
pub struct Field<'a> {
value: &'a dyn Any,
}
impl<'a> Field<'a> {
fn new(value: &'a dyn Any) -> Self {
Field { value }
}
#[inline]
pub fn is<T: Any>(&self) -> bool {
self.value.is::<T>()
}
#[inline]
pub fn type_id(&self) -> TypeId {
self.value.type_id()
}
#[inline]
pub fn get<T: Any>(&self) -> Option<&T> {
self.value.downcast_ref::<T>()
}
#[inline]
pub fn as_any(&self) -> &dyn Any {
self.value
}
#[inline]
pub fn is_slice<T: Any>(&self) -> bool {
self.is::<&[T]>()
}
#[cfg(feature = "alloc")]
pub fn as_slice<T: Any>(&self) -> Option<&[T]> {
get_downcast_ref!(
self.value,
&[T] => |&v| Some(v),
Vec<T> => |v| Some(v.as_slice())
)
}
#[cfg(not(feature = "alloc"))]
#[inline]
pub fn as_slice<T: Any>(&self) -> Option<&[T]> {
self.get().copied()
}
#[cfg(feature = "alloc")]
#[inline]
pub fn is_vec<T: Any>(&self) -> bool {
self.is::<Vec<T>>()
}
#[cfg(feature = "alloc")]
#[inline]
pub fn is_string(&self) -> bool {
self.is::<String>()
}
#[inline]
pub fn is_str(&self) -> bool {
self.is::<&str>()
}
#[cfg(feature = "alloc")]
pub fn as_str(&self) -> Option<&str> {
get_downcast_ref!(
self.value,
&str => |&v| Some(v),
String => |v| Some(v.as_str())
)
}
#[cfg(not(feature = "alloc"))]
#[inline]
pub fn as_str(&self) -> Option<&str> {
self.get().copied()
}
is_type_method!(bool);
is_type_method!(u8, u16, u32, u64, u128, usize);
is_type_method!(i8, i16, i32, i64, i128, isize);
is_type_method!(f32, f64);
as_type_method!(bool);
as_type_method! {
u8 { u16 | u32 | u64 | u128 | usize => |&v| v.try_into().ok() },
u16 {
u8 => |&v| Some(v.into()),
u32 | u64 | u128 | usize => |&v| v.try_into().ok(),
},
u32 {
u16 | u8 => |&v| Some(v.into()),
u64 | u128 | usize => |&v| v.try_into().ok(),
},
u64 {
u32 | u16 | u8 => |&v| Some(v.into()),
u128 | usize => |&v| v.try_into().ok(),
},
u128 {
u8 | u16 | u32 | u64 => |&v| Some(v.into()),
usize => |&v| v.try_into().ok(),
},
usize {
u16 | u8 => |&v| Some(v.into()),
u32 | u64 | u128 => |&v| v.try_into().ok(),
},
}
as_type_method! {
i8 { i16 | i32 | i64 | i128 | isize => |&v| v.try_into().ok() },
i16 {
i8 => |&v| Some(v.into()),
i32 | i64 | i128 | isize => |&v| v.try_into().ok(),
},
i32 {
i16 | i8 => |&v| Some(v.into()),
i64 | i128 | isize => |&v| v.try_into().ok(),
},
i64 {
i32 | i16 | i8 => |&v| Some(v.into()),
i128 | isize => |&v| v.try_into().ok(),
},
i128 {
i8 | i16 | i32 | i64 => |&v| Some(v.into()),
isize => |&v| v.try_into().ok(),
},
isize {
i16 | i8 => |&v| Some(v.into()),
i32 | i64 | i128 => |&v| v.try_into().ok(),
},
}
as_type_method! {
f32,
f64 { f32 => |&v| Some(v.into()) }
}
}
#[derive(Debug)]
pub struct FieldMut<'a> {
value: &'a mut dyn Any,
}
impl<'a> FieldMut<'a> {
fn new(value: &'a mut dyn Any) -> Self {
FieldMut { value }
}
#[inline]
pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
self.value.downcast_mut::<T>()
}
#[inline]
pub fn as_any_mut(&mut self) -> &mut dyn Any {
self.value
}
#[inline]
pub fn set<T: Any>(&mut self, value: T) -> bool {
self.replace(value).is_some()
}
#[inline]
pub fn replace<T: Any>(&mut self, value: T) -> Option<T> {
self.get_mut().map(|dest| mem::replace(dest, value))
}
#[inline]
pub fn swap<T: Any>(&mut self, value: &mut T) -> bool {
self.get_mut().map(|dest| mem::swap(dest, value)).is_some()
}
#[inline]
pub fn take<T: Any + Default>(&mut self) -> Option<T> {
self.replace(T::default())
}
#[cfg(feature = "alloc")]
#[inline]
pub fn as_vec_mut<T: Any>(&mut self) -> Option<&mut Vec<T>> {
self.get_mut::<Vec<T>>()
}
#[cfg(feature = "alloc")]
as_type_mut_method!(String {
example_value: String::from("bar")
});
as_type_mut_method!(bool {
example_value: true
});
as_type_mut_method!(u8, u16, u32, u64, u128, usize);
as_type_mut_method!(i8, i16, i32, i64, i128, isize);
as_type_mut_method!(f32, f64);
}
impl<'a> AsRef<Field<'a>> for FieldMut<'a> {
fn as_ref(&self) -> &Field<'a> {
unsafe { &*(self as *const FieldMut).cast::<Field>() }
}
}
impl<'a> ops::Deref for FieldMut<'a> {
type Target = Field<'a>;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
#[derive(Clone)]
pub struct Fields<'a> {
access: &'a dyn FieldAccess,
field_names: slice::Iter<'a, &'static str>,
}
impl<'a> Fields<'a> {
fn new(access: &'a dyn FieldAccess) -> Self {
Fields {
access,
field_names: access.field_names().iter(),
}
}
}
impl<'a> fmt::Debug for Fields<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.access.field_names()).finish()
}
}
impl<'a> Iterator for Fields<'a> {
type Item = (&'static str, Field<'a>);
fn next(&mut self) -> Option<Self::Item> {
self.field_names
.next()
.and_then(|&name| self.access.field(name).map(|field| (name, field)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.field_names.size_hint()
}
}
impl<'a> DoubleEndedIterator for Fields<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.field_names
.next_back()
.and_then(|&name| self.access.field(name).map(|field| (name, field)))
}
}
impl<'a> ExactSizeIterator for Fields<'a> {}
impl<'a> FusedIterator for Fields<'a> {}