Patchable
A Rust library for deriving patch types and applying patches efficiently to update target types.
Patchable gives each struct a companion patch type plus trait implementations for applying partial updates. It focuses on compact patch representations and efficient updates.
Note: Each struct has one companion patch struct, and each patch struct corresponds to one struct.
Why Patchable?
Patchable shines when you need to persist and update state without hand-maintaining parallel state structs. A common use case is durable execution: save only true state while skipping non-state fields (caches, handles, closures), then restore or update state incrementally.
Typical scenarios include:
- Durable or event-sourced systems where only state fields should be persisted.
- Streaming or real-time pipelines that receive incremental updates.
- Syncing or transporting partial state over the network.
The provided derive macros handle the heavy lifting; they generate companion patch types and patch logic. See Features and How It Works for details.
Table of Contents
Features
- Automatic Patch Type Generation: Derives a companion
Patchstruct for any struct annotated with#[derive(Patchable)] - Recursive Patching: Use the
#[patchable]attribute to mark fields that require recursive patching - Smart Exclusion: Excludes fields marked with
#[patchable(skip)] - Serde Integration (default): Generated patch types automatically implement
serde::DeserializeandClone - Generic Support: Full support for generic types with automatic trait bound inference
- Optional
FromDerive: EnableFrom<Struct>forStructPatchwith theimpl_fromfeature #[patchable_model]Attribute Macro: Auto-derivesPatchableandPatch, and (with defaultserde) addsserde::Serialize- Zero Runtime Overhead: All code generation happens at compile time
Installation
MSRV: Rust 1.85 (edition 2024).
Add this to your Cargo.toml:
[]
= "0.5.2" # You can use the latest version
The serde feature is enabled by default. Disable default features to opt out:
[]
= { = "0.5.2", = false }
Enable From<Struct> generation:
[]
= { = "0.5.2", = ["impl_from"] }
Usage
Basic Example
use ;
use ;
Using #[patchable_model]
The simplest way to use this library is the attribute macro:
use patchable_model;
#[patchable_model] always adds Patchable and Patch derives. With the default
serde feature enabled, it also adds serde::Serialize and injects #[serde(skip)]
for fields marked #[patchable(skip)].
Add any other derives you need (for example, Deserialize) alongside it.
Skipping Fields
Fields can be excluded from patching using #[patchable(skip)]:
use patchable_model;
use Deserialize;
Fields marked with #[patchable(skip)] are excluded from the generated patch type. If you use
#[patchable_model] with the default serde feature enabled, those fields also receive
#[serde(skip)] so serialized state and patches stay aligned.
If you derive Patchable/Patch directly, add #[serde(skip)] yourself when you want
serialization to match patching behavior.
Nested Patchable Structs
The macros fully support generic types:
use ;
use Serialize;
The macros automatically:
- Preserve only the generic parameters used by non-skipped fields
- Add appropriate trait bounds (
Clone,Patchable,Patch) based on field usage - Generate correctly parameterized patch types
Fallible Patching
The TryPatch trait allows for fallible updates, which is useful when patch application requires validation:
use ;
use fmt;
;
Limitations
- Only structs are supported (enums and unions are not).
- Lifetime parameters are not supported.
#[patchable]currently only supports simple generic types (not complex types likeVec<T>).- Generated patch types derive
CloneandDeserializebut notSerialize(by design).
How It Works
When you derive Patchable on a struct, for instance, Struct:
-
Companion Patch Type: The macro generates
StructPatch, which mirrors the original structure but only includes fields that are part of the patch. Here are the rules:- Each field marked with
#[patchable]inStructare typed with<FieldType as Patchable>::PatchinStructPatch. - Fields marked with
#[patchable(skip)]are excluded. - The left fields are copied directly with their original types.
- Each field marked with
-
Trait Implementation: The macro implements
PatchableforStructand setstype Patch = StructPatch(see the API reference for the exact trait definition). -
Serialized State to Patch: If you serialize a
Structinstance, that serialized value can be deserialized into<Struct as Patchable>::Patch, which yields a patch representing the serialized state.
When you derive Patch on a struct:
-
Patch Method: The
patchmethod updates the struct:- Regular fields are directly assigned from the patch
#[patchable]fields are recursively patched via their ownpatchmethod
-
Trait Implementation: The macro generates
Patchimplementation for the target struct (see API reference for the exact trait definitions).
API Reference
#[patchable_model]
Attribute macro that injects Patchable and Patch derives for a struct.
Behavior:
- Adds
#[derive(Patchable, Patch)]to the target struct. - With the default
serdefeature enabled, it also derivesserde::Serializeand applies#[serde(skip)]to fields annotated with#[patchable(skip)].
#[derive(Patchable)]
Generates the companion {StructName}Patch type and implements Patchable for a struct.
Requirements:
- Must be applied to a struct (not enums or unions)
- Does not support lifetime parameters (borrowed fields)
- Works with named, unnamed (tuple), and unit structs
#[derive(Patch)]
Derives the Patch trait implementation for a struct.
Requirements:
- Must be applied to a struct (not enums or unions)
- Does not support lifetime parameters (borrowed fields)
- Works with named, unnamed (tuple), and unit structs
- The target type must implement
Patchable(derive it or implement manually)
#[patchable] Attribute
Marks a field for recursive patching.
Requirements:
- The types of fields with
#[patchable]must implementPatch - Currently only supports simple generic types (not complex types like
Vec<T>)
Patchable Trait
Patch: The associated patch type (automatically generated as{StructName}Patchwhen#[derive(Patchable)]is applied)
Patch Trait
patch: Method to apply a patch to the current instance
TryPatch Trait
A fallible variant of Patch for cases where applying a patch might fail.
try_patch: Applies the patch, returning aResult. A blanket implementation exists for all types that implementPatch(whereErrorisstd::convert::Infallible).
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details on how to get started.
License
This project is licensed under the MIT License and Apache-2.0 License.
Related Projects
- serde - Serialization framework that integrates seamlessly with Patchable
Changelog
See CHANGELOG.md for release notes and version history.