pud-macros

Crates.iopud-macros
lib.rspud-macros
version1.0.0
created_at2025-12-21 20:26:16.35989+00
updated_at2025-12-27 14:24:31.713479+00
descriptionGenerate typed, composable, no-std-friendly modifications (“puds”) for Rust structs.
homepagehttps://github.com/vic1707/pud
repositoryhttps://github.com/vic1707/pud
max_upload_size
id1998537
size23,947
Victor LEFEBVRE (vic1707)

documentation

https://docs.rs/pud

README

pud

github crates.io docs.rs lines maintenance

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)
    ),
    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

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.

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)
    ),
)]
#[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.

Commit count: 0

cargo fmt