From bc709bd48bbfbbd82354e3e747937a430548372d Mon Sep 17 00:00:00 2001 From: Nixon <43715558+nixonyh@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:40:19 +0800 Subject: [PATCH] Add registry feature --- Cargo.toml | 6 +- README.md | 31 +++++---- src/accessor.rs | 174 +++++------------------------------------------- src/field.rs | 15 +++-- src/lib.rs | 57 +--------------- src/registry.rs | 172 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 230 deletions(-) create mode 100644 src/registry.rs diff --git a/Cargo.toml b/Cargo.toml index 57a67f3..5755485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,8 @@ keywords = ["macro", "field", "path", "accessor", "registry"] categories = ["no-std", "rust-patterns"] [dependencies] -hashbrown = { version = "0.16", default-features = false, features = ["default-hasher", "inline-more"] } +hashbrown = { version = "0.16", default-features = false, features = ["default-hasher", "inline-more"], optional = true } + +[features] +default = ["registry"] +registry = ["dep:hashbrown"] diff --git a/README.md b/README.md index 5ef468a..5424629 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,37 @@ [![CI](https://github.com/voxell-tech/field_path/workflows/CI/badge.svg)](https://github.com/voxell-tech/field_path/actions) [![Discord](https://img.shields.io/discord/442334985471655946.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Mhnyp6VYEQ) -**`field_path`** provides a lightweight and type-safe abstraction -for referencing and accessing nested fields within structs. +# Field Path + +**Field Path** provides a lightweight and type-safe abstraction for +referencing and accessing nested fields within structs. The crate is designed to make it easier to generically inspect or -mutate fields without relying on heavy reflection systems or -unsafe code. It does this through a combination of field -identifiers and accessors that preserve type information. +mutate fields without relying on heavy reflection systems or unsafe +code. It does this through a combination of field identifiers and +accessors that preserve type information. ## Core Concepts - `Field`: Represents a unique, type-safe identifier for a field path within a struct. -- `Accessor`: A generic wrapper providing read and write - access to a field. +- `Accessor`: A generic wrapper providing read and write access + to a field. - `FieldAccessorRegistry`: A mapping between fields and their accessors for lookup and dynamic use. -Together, these components allow building flexible systems that -can access or manipulate struct data without tightly coupling to -specific types. + (only available with the "registry" feature, enabled by default) + +Together, these components allow building flexible systems that can +access or manipulate struct data without tightly coupling to specific +types. ## Example -```rs -use field_path::accessor::{FieldAccessorRegistry, accessor}; -use field_path::field::field; +```rust +use field_path::registry::FieldAccessorRegistry; +use field_path::field; +use field_path::accessor; #[derive(Default)] struct Vec2 { diff --git a/src/accessor.rs b/src/accessor.rs index 1439f3d..f45d31f 100644 --- a/src/accessor.rs +++ b/src/accessor.rs @@ -1,29 +1,32 @@ -//! Accessor system for mapping source structures to target fields. +//! Accessors for mapping source structures to target fields. //! -//! This module provides both typed [`Accessor`]s (compile-time -//! source/target types) and type-erased [`UntypedAccessor`]s -//! (runtime checked). +//! This module defines the core [`Accessor`] and [`UntypedAccessor`] +//! types, providing a way to abstract over field access within a +//! source structure. //! -//! They can be registered and retrieved via the [`AccessorRegistry`]. +//! Use the [`accessor!`] macro to safely generate accessors. It +//! ensures that the immutable and mutable paths to a field are +//! identical, preventing logical errors. use core::any::TypeId; -use core::hash::Hash; -use hashbrown::HashMap; -use crate::field::{Field, UntypedField}; +// For docs. +#[expect(unused_imports)] +use crate::accessor; /// 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 +/// ## 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 +/// ## Example +/// /// ``` /// use field_path::accessor::Accessor; /// @@ -69,9 +72,11 @@ impl Accessor { /// Creates an [`Accessor`] that ensures the fields being accessed are /// correct for both immutable and mutable reference. /// -/// # Example +/// ## Example +/// /// ``` -/// use field_path::accessor::{Accessor, accessor}; +/// use field_path::accessor; +/// use field_path::accessor::Accessor; /// /// struct Foo { value: i32 } /// @@ -97,7 +102,6 @@ macro_rules! accessor { ) }; } -pub use accessor; /// A type-erased version of [`Accessor`]. /// @@ -171,106 +175,6 @@ 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 -/// retrieval as typed [`Accessor`]s with runtime checking. -/// -/// # Example -/// ``` -/// use field_path::accessor::{AccessorRegistry, accessor}; -/// -/// struct Foo { value: i32 } -/// -/// let mut registry = AccessorRegistry::new(); -/// registry.register("foo", accessor!(::value)); -/// -/// let accessor = registry.get::(&"foo").unwrap(); -/// let mut foo = Foo { value: 123 }; -/// -/// assert_eq!(accessor.get_ref(&foo), &123); -/// *accessor.get_mut(&mut foo) = 999; -/// assert_eq!(foo.value, 999); -/// ``` -#[derive(Debug)] -pub struct AccessorRegistry { - accessors: HashMap, -} - -impl AccessorRegistry { - /// Construct an empty [`AccessorRegistry`]. - pub fn new() -> Self { - Self { - accessors: HashMap::new(), - } - } -} - -impl AccessorRegistry { - /// Registers an [`UntypedAccessor`] for a given key. - /// - /// Will overwrite existing accessor. - pub fn register( - &mut self, - key: K, - accessor: impl Into, - ) { - self.accessors.insert(key, accessor.into()); - } - - /// Retrieve a typed [`Accessor`] from the registry. - /// - /// Returns an [`AccessorRegErr`] if the key does not exist or - /// if the types do not match. - pub fn get( - &self, - key: &K, - ) -> Result, AccessorRegErr> { - self.accessors - .get(key) - .ok_or(AccessorRegErr::KeyNotFound)? - .typed() - .ok_or(AccessorRegErr::TypeMismatch) - } -} - -impl Default for AccessorRegistry { - fn default() -> Self { - Self { - accessors: HashMap::new(), - } - } -} - -unsafe impl Send for AccessorRegistry {} -unsafe impl Sync for AccessorRegistry {} - -/// Possible error variants when getting an [`Accessor`] -/// from the [`AccessorRegistry`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AccessorRegErr { - /// The requested key was not found in the registry. - KeyNotFound, - /// The [`Accessor`] exists but the source/target types did - /// not match. - TypeMismatch, -} - #[cfg(test)] mod tests { use super::*; @@ -308,48 +212,4 @@ mod tests { let wrong: Option> = untyped.typed(); assert!(wrong.is_none()); } - - #[test] - fn registry_register_and_get_success() { - let mut registry: AccessorRegistry<&'static str> = - AccessorRegistry::new(); - - registry.register("foo_x", accessor!(::x)); - registry.register("foo_y", accessor!(::y)); - - let mut foo = Foo { x: 10, y: 1.5 }; - - let x_accessor = registry.get::(&"foo_x").unwrap(); - assert_eq!((x_accessor.ref_fn)(&foo), &10); - - let y_accessor = registry.get::(&"foo_y").unwrap(); - assert_eq!((y_accessor.ref_fn)(&foo), &1.5); - - // Mutate via accessor - *(x_accessor.mut_fn)(&mut foo) = 77; - *(y_accessor.mut_fn)(&mut foo) = 2.5; - - assert_eq!(foo.x, 77); - assert_eq!(foo.y, 2.5); - } - - #[test] - fn registry_key_not_found_error() { - let registry: AccessorRegistry<&'static str> = - AccessorRegistry::new(); - - let res = registry.get::(&"missing"); - assert!(matches!(res, Err(AccessorRegErr::KeyNotFound))); - } - - #[test] - fn registry_type_mismatch_error() { - let mut registry: AccessorRegistry<&'static str> = - AccessorRegistry::new(); - - 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 978c960..9f622de 100644 --- a/src/field.rs +++ b/src/field.rs @@ -11,6 +11,10 @@ use core::any::TypeId; use core::marker::PhantomData; +// For docs. +#[expect(unused_imports)] +use crate::field; + /// A statically typed field path from a source type `S` to a target /// type `T`. /// @@ -28,7 +32,9 @@ use core::marker::PhantomData; /// /// # Example /// ``` -/// use field_path::field::{Field, field, stringify_field}; +/// use field_path::field::Field; +/// use field_path::field; +/// use field_path::stringify_field; /// /// struct Player { /// name: String, @@ -128,7 +134,8 @@ impl _FieldBuilder { /// # Example /// /// ``` -/// use field_path::field::{Field, field}; +/// use field_path::field::Field; +/// use field_path::field; /// /// struct Player { /// name: String, @@ -150,7 +157,6 @@ macro_rules! field { .build() }; } -pub use field; /// A type-erased version of [`Field`]. It uniquely identifies a /// target field path within a source `struct`. @@ -256,7 +262,7 @@ where /// # Example /// /// ``` -/// use field_path::field::stringify_field; +/// use field_path::stringify_field; /// /// let stringify = stringify_field!(::translation::x); /// assert_eq!(stringify, "::translation::x"); @@ -267,7 +273,6 @@ macro_rules! stringify_field { concat!($("::", stringify!($field),)*) }; } -pub use stringify_field; #[cfg(test)] mod tests { diff --git a/src/lib.rs b/src/lib.rs index 9ee6e7e..a541582 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,58 +1,7 @@ -//! [`Field`]: field::Field -//! [`Accessor`]: accessor::Accessor -//! [`FieldAccessorRegistry`]: accessor::FieldAccessorRegistry -//! -//! # Field Path -//! -//! **`field_path`** provides a lightweight and type-safe abstraction -//! for referencing and accessing nested fields within structs. -//! -//! The crate is designed to make it easier to generically inspect or -//! mutate fields without relying on heavy reflection systems or -//! unsafe code. It does this through a combination of field -//! identifiers and accessors that preserve type information. -//! -//! ## Core Concepts -//! -//! - **[`Field`]**: Represents a unique, type-safe identifier for a -//! field path within a struct. -//! - **[`Accessor`]**: A generic wrapper providing read and write -//! access to a field. -//! - **[`FieldAccessorRegistry`]**: A mapping between fields and their -//! accessors for lookup and dynamic use. -//! -//! Together, these components allow building flexible systems that -//! can access or manipulate struct data without tightly coupling to -//! specific types. -//! -//! ## Example -//! -//! ``` -//! use field_path::accessor::{FieldAccessorRegistry, accessor}; -//! use field_path::field::field; -//! -//! #[derive(Default)] -//! struct Vec2 { -//! pub x: T, -//! pub y: T, -//! } -//! -//! let mut registry = FieldAccessorRegistry::default(); -//! let field = field!(>::x); -//! -//! // Register accessors. -//! registry.register_typed(field, accessor!(>::x)); -//! -//! // Access field generically. -//! let mut v = Vec2::default(); -//! let accessor = -//! registry.get::, f32>(&field.untyped()).unwrap(); -//! -//! *accessor.get_mut(&mut v) = 42.0; -//! assert_eq!(accessor.get_ref(&v), &42.0); -//! ``` - +#![doc = include_str!("../README.md")] #![no_std] pub mod accessor; pub mod field; +#[cfg(feature = "registry")] +pub mod registry; diff --git a/src/registry.rs b/src/registry.rs new file mode 100644 index 0000000..5c68822 --- /dev/null +++ b/src/registry.rs @@ -0,0 +1,172 @@ +//! Storage for data accessors. +//! +//! The [`AccessorRegistry`] acts as a dynamic lookup table for +//! accessors. It allows developers to register field access logic +//! during initialization and retrieve it later. +//! +//! A [`FieldAccessorRegistry`] is also provided as a type alias of +//! using [`UntypedField`] as the key for the registry. + +use core::hash::Hash; +use hashbrown::HashMap; + +use crate::accessor::{Accessor, UntypedAccessor}; +use crate::field::{Field, UntypedField}; + +/// 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 +/// retrieval as typed [`Accessor`]s with runtime checking. +/// +/// # Example +/// ``` +/// use field_path::registry::AccessorRegistry; +/// use field_path::accessor; +/// +/// struct Foo { value: i32 } +/// +/// let mut registry = AccessorRegistry::new(); +/// registry.register("foo", accessor!(::value)); +/// +/// let accessor = registry.get::(&"foo").unwrap(); +/// let mut foo = Foo { value: 123 }; +/// +/// assert_eq!(accessor.get_ref(&foo), &123); +/// *accessor.get_mut(&mut foo) = 999; +/// assert_eq!(foo.value, 999); +/// ``` +#[derive(Debug)] +pub struct AccessorRegistry { + accessors: HashMap, +} + +impl AccessorRegistry { + /// Construct an empty [`AccessorRegistry`]. + pub fn new() -> Self { + Self { + accessors: HashMap::new(), + } + } +} + +impl AccessorRegistry { + /// Registers an [`UntypedAccessor`] for a given key. + /// + /// Will overwrite existing accessor. + pub fn register( + &mut self, + key: K, + accessor: impl Into, + ) { + self.accessors.insert(key, accessor.into()); + } + + /// Retrieve a typed [`Accessor`] from the registry. + /// + /// Returns an [`AccessorRegErr`] if the key does not exist or + /// if the types do not match. + pub fn get( + &self, + key: &K, + ) -> Result, AccessorRegErr> { + self.accessors + .get(key) + .ok_or(AccessorRegErr::KeyNotFound)? + .typed() + .ok_or(AccessorRegErr::TypeMismatch) + } +} + +impl Default for AccessorRegistry { + fn default() -> Self { + Self { + accessors: HashMap::new(), + } + } +} + +unsafe impl Send for AccessorRegistry {} +unsafe impl Sync for AccessorRegistry {} + +/// Possible error variants when getting an [`Accessor`] +/// from the [`AccessorRegistry`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccessorRegErr { + /// The requested key was not found in the registry. + KeyNotFound, + /// The [`Accessor`] exists but the source/target types did + /// not match. + TypeMismatch, +} + +#[cfg(test)] +mod tests { + use crate::accessor; + + use super::*; + + #[derive(Debug, PartialEq)] + struct Foo { + x: i32, + y: f32, + } + + #[test] + fn registry_register_and_get_success() { + let mut registry: AccessorRegistry<&'static str> = + AccessorRegistry::new(); + + registry.register("foo_x", accessor!(::x)); + registry.register("foo_y", accessor!(::y)); + + let mut foo = Foo { x: 10, y: 1.5 }; + + let x_accessor = registry.get::(&"foo_x").unwrap(); + assert_eq!(x_accessor.get_ref(&foo), &10); + + let y_accessor = registry.get::(&"foo_y").unwrap(); + assert_eq!(y_accessor.get_ref(&foo), &1.5); + + // Mutate via accessor + *x_accessor.get_mut(&mut foo) = 77; + *y_accessor.get_mut(&mut foo) = 2.5; + + assert_eq!(foo.x, 77); + assert_eq!(foo.y, 2.5); + } + + #[test] + fn registry_key_not_found_error() { + let registry: AccessorRegistry<&'static str> = + AccessorRegistry::new(); + + let res = registry.get::(&"missing"); + assert!(matches!(res, Err(AccessorRegErr::KeyNotFound))); + } + + #[test] + fn registry_type_mismatch_error() { + let mut registry: AccessorRegistry<&'static str> = + AccessorRegistry::new(); + + registry.register("foo_x", accessor!(::x)); + + let res = registry.get::(&"foo_x"); + assert!(matches!(res, Err(AccessorRegErr::TypeMismatch))); + } +}