| Crates.io | patchable-macro |
| lib.rs | patchable-macro |
| version | 0.4.0 |
| created_at | 2026-01-17 05:32:04.827199+00 |
| updated_at | 2026-01-24 05:59:28.740687+00 |
| description | Procedural macros for the patchable crate |
| homepage | |
| repository | https://github.com/ShapelessCat/patchable |
| max_upload_size | |
| id | 2050006 |
| size | 26,067 |
A Rust library with for automatically deriving patch types and implementing efficient state updates for target types.
A Patchable trait is provided:
A derive macro that automatically generates a companion "patch" type for your target struct and implements Patchable
on the target type.
This enables efficient partial updates of struct instances by applying patches, which is particularly useful for:
State management in event-driven systems
Incremental updates in streaming applications
Serialization/deserialization of state changes
State struct for any struct annotated with #[derive(Patchable)]#[patchable] attribute to mark fields that require recursive patching#[serde(skip)] and #[serde(skip_serializing)], and PhantomData to keep patches lean.serde::Deserialize and CloneAdd this to your Cargo.toml:
[dependencies]
patchable = "0.1.0"
use patchable::Patchable;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Patchable)]
struct User {
id: u64,
name: String,
email: String,
}
fn main() {
let mut user = User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
// Serialize the current state
let state_json = serde_json::to_string(&user).unwrap();
// Deserialize into a patch
let patch: UserState = serde_json::from_str(&state_json).unwrap();
let mut default = User::default();
// Apply the patch
default.patch(patch);
assert_eq!(default, user);
}
Fields can be excluded from patching using serde attributes:
use patchable::Patchable;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, Patchable)]
struct Measurement<T, F> {
value: T,
#[serde(skip)]
compute_fn: F,
}
Fields marked with #[serde(skip)] or #[serde(skip_serializing)] are automatically excluded from the generated patch type.
The macro fully supports generic types:
use patchable::Patchable;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, Patchable)]
struct Container<Closure> {
#[serde(skip)]
computation_logic: Closure, // Not a part of state
metadata: String,
}
#[derive(Clone, Debug, Serialize, Patchable)]
struct Wrapper<T, Closure> {
data: T,
#[patchable]
inner: Container<Closure>,
}
The macro automatically:
Clone, Patchable) based on field usageThe TryPatch trait allows for fallible updates, which is useful when patch application requires validation:
use patchable::TryPatch;
use std::fmt;
struct Config {
limit: u32,
}
#[derive(Clone)]
struct ConfigPatch {
limit: u32,
}
#[derive(Debug)]
struct InvalidConfigError;
impl fmt::Display for InvalidConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "limit cannot be zero")
}
}
impl std::error::Error for InvalidConfigError {}
impl TryPatch for Config {
type Patch = ConfigPatch;
type Error = InvalidConfigError;
fn try_patch(&mut self, patch: Self::Patch) -> Result<(), Self::Error> {
if patch.limit == 0 {
return Err(InvalidConfigError);
}
self.limit = patch.limit;
Ok(())
}
}
When you derive Patchable on a struct:
Patch Type Generation: A companion struct named {StructName}State is generated
#[patchable] use their own patch types (T::Patch)#[serde(skip)], #[serde(skip_serializing)] or PhantomData are excludedTrait Implementation: The Patchable trait is implemented:
pub trait Patchable {
type Patch: Clone;
fn patch(&mut self, patch: Self::Patch);
}
Patch Method: The patch method updates the struct:
#[patchable] fields are recursively patched via their own patch method#[derive(Patchable)]Derives the Patchable trait for a struct.
Requirements:
#[patchable] AttributeMarks a field for recursive patching.
Requirements:
#[patchable] must implement PatchableVec<T>)Patchable Traitpub trait Patchable {
type Patch: Clone;
fn patch(&mut self, patch: Self::Patch);
}
Patch: The associated patch type (automatically generated as {StructName}State if #[derive(Patchable)] is
applied)
patch: Method to apply a patch to the current instance
TryPatch TraitA fallible variant of Patchable for cases where applying a patch might fail.
pub trait TryPatch {
type Patch: Clone;
type Error: std::error::Error + Send + Sync + 'static;
fn try_patch(&mut self, patch: Self::Patch) -> Result<(), Self::Error>;
}
try_patch: Applies the patch, returning a Result. A blanket implementation exists for all types that implement Patchable (where Error is std::convert::Infallible).MIT
Contributions are welcome! Please feel free to submit issues or pull requests.