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 the [examples](https://github.com/Crazytieguy/structural-typing-rs/tree/master/examples) directory 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#[doc(hidden)]
52pub use derive_where;
53
54/// Construct a `FieldSet` by selecting fields from a module.
55///
56/// # Syntax
57/// - `select!(module: field)` - field is Present
58/// - `select!(module: ?field)` - field is Optional
59/// - `select!(module: all)` - all fields Present (expands to `module::with::all::Present`)
60/// - Multiple fields compose: `select!(module: field1, ?field2)` expands to `module::with::field1::Present<module::with::field2::Optional>`
61///
62/// # Examples
63/// ```rust
64/// # use structural_typing::{structural, select};
65/// # #[structural]
66/// # struct User { name: String, email: String, id: u64 }
67/// // For User { name, email, id }
68/// type NameAndEmail = select!(user: name, email);
69/// type NameOptionalEmail = select!(user: name, ?email);
70/// type AllPresent = select!(user: all);
71/// ```
72#[macro_export]
73macro_rules! select {
74 // Error: invalid + prefix
75 ($($module:ident)::+ : + $field:ident $($rest:tt)*) => {
76 compile_error!("Invalid prefix '+' in select!. Use 'field' for Present or '?field' for Optional")
77 };
78
79 // Error: invalid - prefix
80 ($($module:ident)::+ : - $field:ident $($rest:tt)*) => {
81 compile_error!("Invalid prefix '-' in select!. Use 'field' for Present or '?field' for Optional")
82 };
83
84 // Error: empty field list
85 ($($module:ident)::+ :) => {
86 compile_error!("select! requires at least one field")
87 };
88
89 // Single field: Present
90 ($($module:ident)::+ : $field:ident) => {
91 $($module)::+::with::$field::Present
92 };
93
94 // Single field: Present with trailing comma
95 ($($module:ident)::+ : $field:ident,) => {
96 $($module)::+::with::$field::Present
97 };
98
99 // Single field: Optional
100 ($($module:ident)::+ : ? $field:ident) => {
101 $($module)::+::with::$field::Optional
102 };
103
104 // Single field: Optional with trailing comma
105 ($($module:ident)::+ : ? $field:ident,) => {
106 $($module)::+::with::$field::Optional
107 };
108
109 // Multiple fields: Present + rest
110 ($($module:ident)::+ : $field:ident, $($rest:tt)+) => {
111 $($module)::+::with::$field::Present<select!($($module)::+ : $($rest)+)>
112 };
113
114 // Multiple fields: Optional + rest
115 ($($module:ident)::+ : ? $field:ident, $($rest:tt)+) => {
116 $($module)::+::with::$field::Optional<select!($($module)::+ : $($rest)+)>
117 };
118}