diff --git a/README.md b/README.md index e9dd491..7f45dfc 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ identifiers and accessors that preserve type information. ## Core Concepts -- `Field` — Represents a unique, type-safe identifier for a +- `Field`: Represents a unique, type-safe identifier for a field path within a struct. -- `Accessor` — A generic wrapper providing read and write +- `Accessor`: A generic wrapper providing read and write access to a field. -- `FieldAccessorRegistry` — A mapping between fields and their +- `FieldAccessorRegistry`: A mapping between fields and their accessors for lookup and dynamic use. Together, these components allow building flexible systems that diff --git a/src/accessor.rs b/src/accessor.rs index 2401b9c..99a81db 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -10,13 +10,19 @@ use core::any::TypeId; use core::hash::Hash; use hashbrown::HashMap; -use crate::field::UntypedField; +use crate::field::{Field, UntypedField}; /// A typed accessor to a field of type `T` within a source type `S`. /// /// This holds both immutable and mutable function pointers, which /// allows retrieving references to the target field inside a source. /// +/// # Validation +/// +/// The [`accessor!`] macro ensures that both immutable and mutable +/// references are pointing towards the same field. Constructing +/// `Accessor` manually may result in mismatches. +/// /// # Example /// ``` /// use field_path::accessor::Accessor; @@ -26,11 +32,11 @@ use crate::field::UntypedField; /// fn ref_fn(s: &Foo) -> &i32 { &s.value } /// fn mut_fn(s: &mut Foo) -> &mut i32 { &mut s.value } /// -/// let accessor = Accessor { ref_fn, mut_fn }; +/// const FOO_ACC: Accessor = Accessor::new(ref_fn, mut_fn); /// let mut foo = Foo { value: 42 }; /// -/// assert_eq!(*(accessor.ref_fn)(&foo), 42); -/// *(accessor.mut_fn)(&mut foo) = 999; +/// assert_eq!(FOO_ACC.get_ref(&foo), &42); +/// *FOO_ACC.get_mut(&mut foo) = 999; /// assert_eq!(foo.value, 999); /// ``` #[derive(Debug, Clone, Copy)] @@ -40,11 +46,59 @@ pub struct Accessor { } impl Accessor { + pub const fn new( + ref_fn: fn(&S) -> &T, + mut_fn: fn(&mut S) -> &mut T, + ) -> Self { + Self { ref_fn, mut_fn } + } + + pub fn get_ref<'a>(&self, source: &'a S) -> &'a T { + (self.ref_fn)(source) + } + + pub fn get_mut<'a>(&self, source: &'a mut S) -> &'a mut T { + (self.mut_fn)(source) + } + pub fn untyped(self) -> UntypedAccessor { UntypedAccessor::new(self.ref_fn, self.mut_fn) } } +/// Creates an [`Accessor`] that ensures the fields being accessed are +/// correct for both immutable and mutable reference. +/// +/// # Example +/// ``` +/// use field_path::accessor::{Accessor, accessor}; +/// +/// struct Foo { value: i32 } +/// +/// const FOO_ACC: Accessor = accessor!(::value); +/// let mut foo = Foo { value: 42 }; +/// +/// assert_eq!(FOO_ACC.get_ref(&foo), &42); +/// *FOO_ACC.get_mut(&mut foo) = 999; +/// assert_eq!(foo.value, 999); +/// ``` +#[macro_export] +macro_rules! accessor { + (<$source:ty>) => { + $crate::accessor::Accessor::new( + |s: &$source| s, + |s: &mut $source| s, + ) + }; + (<$source:ty>$(::$field:tt)+) => { + $crate::accessor::Accessor::new( + |s: &$source| &s$(.$field)+, + |s: &mut $source| &mut s$(.$field)+ + ) + }; +} +pub use accessor; + /// A type-erased version of [`Accessor`]. /// /// Stores the raw function pointers as `*const ()` along with @@ -118,6 +172,18 @@ impl From> for UntypedAccessor { /// An [`AccessorRegistry`] using [`UntypedField`] as the key type. pub type FieldAccessorRegistry = AccessorRegistry; +impl FieldAccessorRegistry { + /// Registers a [`Field`] and [`Accessor`] pair in a type-safe + /// manner. + pub fn register_typed( + &mut self, + field: Field, + accessor: Accessor, + ) { + self.register(field.untyped(), accessor); + } +} + /// A registry mapping keys to [`UntypedAccessor`]s. /// /// Provides convenient registration of typed accessors and @@ -125,15 +191,12 @@ pub type FieldAccessorRegistry = AccessorRegistry; /// /// # Example /// ``` -/// use field_path::accessor::{Accessor, AccessorRegistry}; +/// use field_path::accessor::{AccessorRegistry, accessor}; /// /// struct Foo { value: i32 } /// -/// fn ref_fn(s: &Foo) -> &i32 { &s.value } -/// fn mut_fn(s: &mut Foo) -> &mut i32 { &mut s.value } -/// /// let mut registry = AccessorRegistry::new(); -/// registry.register("foo", Accessor { ref_fn, mut_fn }); +/// registry.register("foo", accessor!(::value)); /// /// let accessor = registry.get::(&"foo").unwrap(); /// let mut foo = Foo { value: 123 }; @@ -216,26 +279,9 @@ mod tests { y: f32, } - fn foo_x_ref(foo: &Foo) -> &i32 { - &foo.x - } - fn foo_x_mut(foo: &mut Foo) -> &mut i32 { - &mut foo.x - } - - fn foo_y_ref(foo: &Foo) -> &f32 { - &foo.y - } - fn foo_y_mut(foo: &mut Foo) -> &mut f32 { - &mut foo.y - } - #[test] fn accessor_roundtrip_typed_untyped() { - let acc: Accessor = Accessor { - ref_fn: foo_x_ref, - mut_fn: foo_x_mut, - }; + let acc: Accessor = accessor!(::x); let untyped = acc.untyped(); let typed_back: Accessor = untyped.typed().unwrap(); @@ -252,10 +298,7 @@ mod tests { #[test] fn untyped_typed_mismatch_fails() { - let acc: Accessor = Accessor { - ref_fn: foo_x_ref, - mut_fn: foo_x_mut, - }; + let acc: Accessor = accessor!(::x); let untyped = acc.untyped(); @@ -269,21 +312,8 @@ mod tests { let mut registry: AccessorRegistry<&'static str> = AccessorRegistry::new(); - registry.register( - "foo_x", - Accessor { - ref_fn: foo_x_ref, - mut_fn: foo_x_mut, - }, - ); - - registry.register( - "foo_y", - Accessor { - ref_fn: foo_y_ref, - mut_fn: foo_y_mut, - }, - ); + registry.register("foo_x", accessor!(::x)); + registry.register("foo_y", accessor!(::y)); let mut foo = Foo { x: 10, y: 1.5 }; @@ -315,13 +345,7 @@ mod tests { let mut registry: AccessorRegistry<&'static str> = AccessorRegistry::new(); - registry.register( - "foo_x", - Accessor { - ref_fn: foo_x_ref, - mut_fn: foo_x_mut, - }, - ); + registry.register("foo_x", accessor!(::x)); let res = registry.get::(&"foo_x"); assert!(matches!(res, Err(AccessorRegErr::TypeMismatch))); diff --git a/src/field.rs b/src/field.rs index 319d076..f7feb1f 100644 --- a/src/field.rs +++ b/src/field.rs @@ -30,19 +30,18 @@ use core::marker::PhantomData; /// /// # Example /// ``` -/// use field_path::field::{Field, field}; +/// use field_path::field::{Field, field, stringify_field}; /// /// struct Player { /// name: String, /// age: u32, /// } /// -/// let name: Field = field!(::name); -/// let age: Field = field!(::age); +/// const PLAYER_AGE: Field = Field::new( +/// stringify_field!(::age) +/// ); /// -/// assert_ne!(name.untyped(), age.untyped()); -/// assert_eq!(name.field_path(), "::name"); -/// assert_eq!(age.field_path(), "::age"); +/// assert_eq!(PLAYER_AGE.field_path(), "::age"); /// ``` #[derive(Debug, Hash, PartialEq, Eq)] pub struct Field { diff --git a/src/lib.rs b/src/lib.rs index e317a3a..9ee6e7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,11 @@ //! //! ## Core Concepts //! -//! - [`Field`] — Represents a unique, type-safe identifier for a +//! - **[`Field`]**: Represents a unique, type-safe identifier for a //! field path within a struct. -//! - [`Accessor`] — A generic wrapper providing read and write +//! - **[`Accessor`]**: A generic wrapper providing read and write //! access to a field. -//! - [`FieldAccessorRegistry`] — A mapping between fields and their +//! - **[`FieldAccessorRegistry`]**: A mapping between fields and their //! accessors for lookup and dynamic use. //! //! Together, these components allow building flexible systems that @@ -28,7 +28,7 @@ //! ## Example //! //! ``` -//! use field_path::accessor::{Accessor, FieldAccessorRegistry}; +//! use field_path::accessor::{FieldAccessorRegistry, accessor}; //! use field_path::field::field; //! //! #[derive(Default)] @@ -38,24 +38,18 @@ //! } //! //! let mut registry = FieldAccessorRegistry::default(); -//! let field = field!(>::x).untyped(); +//! let field = field!(>::x); //! //! // Register accessors. -//! registry.register( -//! field, -//! Accessor { -//! ref_fn: |v: &Vec2| &v.x, -//! mut_fn: |v: &mut Vec2| &mut v.x, -//! }, -//! ); +//! registry.register_typed(field, accessor!(>::x)); //! //! // Access field generically. //! let mut v = Vec2::default(); -//! let accessor = registry.get::, f32>(&field).unwrap(); -//! -//! *(accessor.mut_fn)(&mut v) = 42.0; -//! assert_eq!(*(accessor.ref_fn)(&v), 42.0); +//! let accessor = +//! registry.get::, f32>(&field.untyped()).unwrap(); //! +//! *accessor.get_mut(&mut v) = 42.0; +//! assert_eq!(accessor.get_ref(&v), &42.0); //! ``` #![no_std]