4 releases (2 stable)
Uses new Rust 2024
| 1.1.0 | Mar 26, 2026 |
|---|---|
| 1.0.0 | Dec 27, 2025 |
| 0.0.2 | Dec 24, 2025 |
| 0.0.1 | Dec 21, 2025 |
#625 in Rust patterns
14KB
61 lines
pud
pud is a procedural macro and trait system for generating typed, composable, no-std-friendly modifications (“puds”) for Rust structs.
Core Concepts
[Pud]
A pud is a single atomic modification that can be applied to a target value.
pub trait Pud {
type Target;
fn apply(self, target: &mut Self::Target);
}
A pud consumes itself and mutates its Target in place.
Pudded
Any sized type can receive puds (blanket implementation).
pub trait Pudded: Sized {
fn apply(&mut self, pud: impl Pud<Target = Self>);
fn apply_batch(&mut self, puds: impl Iterator<Item = impl Pud<Target = Self>>);
}
IntoPud / TryIntoPud
IntoPud (and its fallible equivalent TryIntoPud) allows external code to produce a modification without depending on the concrete {StructName}Pud enum generated by #[pud].
This is useful for update producers such as commands, events, or protocol messages, which should be able to request changes to a struct without knowing the name, visibility, or exact shape of its Pud enum.
pub trait IntoPud {
type Pud: Pud;
fn into_pud(self) -> Self::Pud;
}
pub trait TryIntoPud {
type Pud: Pud;
type Error;
fn try_into_pud(self) -> Result<Self::Pud, Self::Error>;
}
TryIntoPud is the fallible variant and is automatically implemented for all IntoPud types using Infallible as the error.
The #[pud] macro
The #[pud] attribute is applied to a struct and generates:
-
A Pud enum named
{StructName}Pud(by default) -
An implementation of
Pud<Target = StructName>for that enum -
Optional visibility, attributes, and renaming control
Note: The generated code is fully #![no_std] compatible and doesn't depend on alloc.
Basic Example
#[::pud::pud]
pub struct Foo {
a: u8,
b: u8,
}
Generates:
pub struct Foo {
a: u8,
b: u8,
}
pub enum FooPud {
A(u8),
B(u8),
}
#[automatically_derived]
impl ::pud::Pud for FooPud {
type Target = Foo;
fn apply(self, target: &mut Self::Target) {
match self {
Self::A(_0) => {
target.a = _0;
}
Self::B(_1) => {
target.b = _1;
}
}
}
}
Struct-Level Settings
Struct-level settings are provided inside the #[pud(...)] attribute.
#[pud(
vis = pub(crate),
attrs(
repr(C),
derive(Debug)
),
phantom_attrs(allow(dead_code)),
group_attrs(
FooBarBaz(allow(non_camel_case_types))
),
rename = CustomPudName
)]
| Setting | Description |
|---|---|
vis = <visibility> |
Visibility of the generated Pud enum |
rename = <Ident> |
Rename the generated Pud enum |
attrs(...) |
Attributes applied to the generated Pud enum |
phantom_attrs(...) |
Attributes applied to the hidden __ generic variant |
group_attrs(...) |
Attributes applied to generated group variants |
phantom_attrs(...)
Applies attributes to the hidden __ variant that is generated when the Pud enum has type generics.
group_attrs(NAME(...), OTHER(...))
Applies attributes to generated group variants by group name.
#[pud(
group_attrs(
FooBarBaz(cfg(feature = "grouped"))
)
)]
Field-Level Settings
Field settings control how individual fields participate in the Pud enum and how updates are applied.
Settings may be comma-separated or split across multiple #[pud(...)] attributes.
attrs(...)
Applies attributes to the generated Pud variant for that field.
#[pud(attrs(cfg(feature = "unstable")))]
foo: u8,
rename = Ident
Renames the generated Pud variant.
#[pud(rename = FOO)]
foo: u8,
Generates
FooPud::FOO(u8) // instead of FooPud::foo(u8)
map(Type >>= expr)
Allows you to use a mapper instead of the field type, mapper can be a function path or a closure expression (Fn(Type) -> field_type)
#[pud(map(u8 >>= u8_to_u16))]
toto: u16,
Generates:
target.toto = u8_to_u16(_0);
flatten = Type
Delegates modification to another Pud type (a glorified map(InnerPud >>= Pud::apply)).
#[pud(flatten = AnotherPud)]
titi: u8,
group = Ident
Groups multiple fields into a single multi-field Pud variant.
#[pud(group = FooBarBaz)]
foo: u8,
#[pud(group = FooBarBaz)]
bar: u8,
#[pud(group = FooBarBaz)]
baz: u8,
Generates:
FooBarBaz(u8, u8, u8) // and appropriate application
Note: Groups do not remove the individual field variants; both coexist.
Full Example
#[::pud::pud(
vis = pub(crate),
attrs(
repr(C),
derive(Debug)
),
group_attrs(
FooBarBaz(cfg(feature = "grouped"))
),
)]
#[derive(Debug)]
pub struct Foo {
#[pud(map(u8 >>= u8_to_u16))]
toto: u16,
#[pud(rename = TATA)]
tata: u8,
#[pud(flatten = AnotherPud)]
titi: u8,
#[pud(group = FooBarBaz)]
foo: u8,
#[pud(group = FooBarBaz)]
bar: u8,
#[pud(group = FooBarBaz)]
baz: u8,
}
Generates:
#[derive(Debug)]
pub struct Foo {
toto: u16,
tata: u8,
titi: u8,
foo: u8,
bar: u8,
baz: u8,
}
#[repr(C)]
#[derive(Debug)]
pub(crate) enum FooPud {
Toto(u8),
TATA(u8),
Titi(AnotherPud),
Foo(u8),
Bar(u8),
Baz(u8),
FooBarBaz(u8, u8, u8),
}
#[automatically_derived]
impl ::pud::Pud for FooPud {
type Target = Foo;
fn apply(self, target: &mut Self::Target) {
match self {
Self::Toto(_0) => { target.toto = (u8_to_u16)(_0); }
Self::TATA(_1) => { target.tata = _1; }
Self::Titi(_2) => { _2.apply(&mut target.titi); }
Self::Foo(_3) => { target.foo = _3; }
Self::Bar(_4) => { target.bar = _4; }
Self::Baz(_5) => { target.baz = _5; }
Self::FooBarBaz(_3, _4, _5) => {
target.foo = _3;
target.bar = _4;
target.baz = _5;
}
}
}
}
Design Philosophy
pud is minimal and explicit: a pud describes what to update, not whether or how to update it.
Producing a modification is a deliberate act—if a value should not change, ideally, no pud should be emitted.
The crate avoids side effects, hidden control flow, or mutable access outside of apply; mapping is pure, and application is mechanical and predictable.
Conditional or state-dependent updates are outside the core design, but may be added later via optional, feature-gated extensions without affecting the current guarantees.
Supported Types and Constraints
Only structs are supported.
Named and tuple structs are allowed; tuple struct fields must be explicitly renamed.
Dependencies
~140KB