structural_typing/
lib.rs

1//! # Structural Typing for Rust
2//!
3//! Track which struct fields are present/absent at the type level.
4//!
5//! ## Quick Example
6//!
7//! ```
8//! use structural_typing::{structural, presence::Present, select};
9//!
10//! #[structural]
11//! struct User { name: String, email: String }
12//!
13//! // Build incrementally - type tracks which fields are set
14//! let user = User::empty().name("Alice".to_owned());
15//!
16//! // Methods can require specific fields
17//! impl<F: user::Fields<name = Present>> User<F> {
18//!     fn greet(&self) -> String {
19//!         format!("Hello, {}!", self.name)
20//!     }
21//! }
22//!
23//! user.greet(); // ✓ Compiles - name is Present
24//! // User::empty().greet(); // ✗ Compile error - name is Absent
25//!
26//! // Use select! to create field sets
27//! type NameAndEmail = select!(user: name, email);
28//! ```
29//!
30//! ## Core Concepts
31//!
32//! - **Field States**: `Present` (has value), `Optional` (has `Option<T>`), `Absent` (no value)
33//! - **Builder API**: `.field(value)` infers presence from type (T → Present, Option<T> → Optional, `PhantomData<T>` → Absent)
34//! - **Type Selection**: `select!(module: field1, ?field2)` macro or type aliases like `with::name::Present<with::email::Optional>`
35//! - **Bounded Impls**: Methods requiring specific fields via trait bounds
36//!
37//! See [examples/](https://github.com/Crazytieguy/structural-typing-rs/tree/master/structural-typing/examples) for comprehensive usage including merge, split, serde integration, and more.
38
39#![deny(missing_docs)]
40#![warn(clippy::pedantic)]
41
42/// Runtime field access trait for checking presence and getting values.
43pub mod access;
44/// Type-level presence markers and traits for field state tracking.
45pub mod presence;
46/// Traits for splitting structs into selected fields and remainder.
47pub mod split;
48
49pub use structural_typing_macros::structural;
50
51/// Construct a `FieldSet` by selecting fields from a module.
52///
53/// # Syntax
54/// - `select!(module: field)` - field is Present
55/// - `select!(module: ?field)` - field is Optional
56/// - `select!(module: all)` - all fields Present (expands to `module::with::all::Present`)
57/// - Multiple fields compose: `select!(module: field1, ?field2)` expands to `module::with::field1::Present<module::with::field2::Optional>`
58///
59/// # Examples
60/// ```rust
61/// # use structural_typing::{structural, select};
62/// # #[structural]
63/// # struct User { name: String, email: String, id: u64 }
64/// // For User { name, email, id }
65/// type NameAndEmail = select!(user: name, email);
66/// type NameOptionalEmail = select!(user: name, ?email);
67/// type AllPresent = select!(user: all);
68/// ```
69#[macro_export]
70macro_rules! select {
71    // Error: invalid + prefix
72    ($($module:ident)::+ : + $field:ident $($rest:tt)*) => {
73        compile_error!("Invalid prefix '+' in select!. Use 'field' for Present or '?field' for Optional")
74    };
75
76    // Error: invalid - prefix
77    ($($module:ident)::+ : - $field:ident $($rest:tt)*) => {
78        compile_error!("Invalid prefix '-' in select!. Use 'field' for Present or '?field' for Optional")
79    };
80
81    // Error: empty field list
82    ($($module:ident)::+ :) => {
83        compile_error!("select! requires at least one field")
84    };
85
86    // Single field: Present
87    ($($module:ident)::+ : $field:ident) => {
88        $($module)::+::with::$field::Present
89    };
90
91    // Single field: Present with trailing comma
92    ($($module:ident)::+ : $field:ident,) => {
93        $($module)::+::with::$field::Present
94    };
95
96    // Single field: Optional
97    ($($module:ident)::+ : ? $field:ident) => {
98        $($module)::+::with::$field::Optional
99    };
100
101    // Single field: Optional with trailing comma
102    ($($module:ident)::+ : ? $field:ident,) => {
103        $($module)::+::with::$field::Optional
104    };
105
106    // Multiple fields: Present + rest
107    ($($module:ident)::+ : $field:ident, $($rest:tt)+) => {
108        $($module)::+::with::$field::Present<select!($($module)::+ : $($rest)+)>
109    };
110
111    // Multiple fields: Optional + rest
112    ($($module:ident)::+ : ? $field:ident, $($rest:tt)+) => {
113        $($module)::+::with::$field::Optional<select!($($module)::+ : $($rest)+)>
114    };
115}