Skip to main content

Patch

Trait Patch 

Source
pub trait Patch<P> {
    // Required methods
    fn apply(&mut self, patch: P);
    fn into_patch(self) -> P;
    fn into_patch_by_diff(self, previous_struct: Self) -> P;
    fn new_empty_patch() -> P;

    // Provided method
    fn apply_with_log<F: FnMut(&str)>(&mut self, patch: P, _log: F) { ... }
}
Expand description

A struct that a patch can be applied to

Deriving Patch will generate a patch struct and an accompanying trait impl so that it can be applied to the original struct.

#[derive(Patch)]
struct Item {
    field_bool: bool,
    field_int: usize,
    field_string: String,
}

// Generated struct
// struct ItemPatch {
//     field_bool: Option<bool>,
//     field_int: Option<usize>,
//     field_string: Option<String>,
// }

§Container attributes

§#[patch(attribute(derive(...)))]

Use this attribute to derive traits on the generated patch struct

#[derive(Patch)]
#[patch(attribute(derive(Debug, Default, Deserialize, Serialize)))]
struct Item;

// Generated struct
// #[derive(Debug, Default, Deserialize, Serialize)]
// struct ItemPatch {}

§#[patch(attribute(...))]

Use this attribute to pass the attributes on the generated patch struct

// This example need `serde` and `serde_with` crates
#[derive(Patch, Debug)]
#[patch(attribute(derive(Serialize, Deserialize, Default)))]
#[patch(attribute(skip_serializing_none))]
struct Item;

// Generated struct
// #[derive(Default, Deserialize, Serialize)]
// #[skip_serializing_none]
// struct ItemPatch {}

§#[patch(name = "...")]

Use this attribute to change the name of the generated patch struct

#[derive(Patch)]
#[patch(name = "ItemOverlay")]
struct Item { }

// Generated struct
// struct ItemOverlay {}

§#[patch(default_log(fn_path))]

Automatically call fn_path with patched field information inside every generated apply call. Has no effect on apply_with_log. The path may be any function path visible at the call site.

When the nesting feature is enabled, fn_path receives both a prefix path and field name. When nesting is disabled, fn_path receives only the field name.

#[derive(Default, Patch)]
struct Item {
    field_int: usize,
    field_string: String,
}

let mut item = Item::default();
let patch = ItemPatch { field_int: Some(1), field_string: None };

#[cfg(feature = "nesting")]
{
    fn log_field(prefixes: &[&str], field: &str) {
        let path = if prefixes.is_empty() {
            field.to_string()
        } else {
            format!("{}.{}", prefixes.join("."), field)
        };
        println!("patched: {path}");
    }

    #[derive(Default, Patch)]
    #[patch(default_log(log_field))]
    struct Config {
        field_int: usize,
        field_string: String,
    }
    let mut config = Config::default();
    config.apply(ConfigPatch { field_int: Some(1), field_string: None });
    // log_field(&[], "field_int") is called automatically
}

#[cfg(not(feature = "nesting"))]
{
    fn log_field(field: &str) {
        println!("patched: {field}");
    }

    #[derive(Default, Patch)]
    #[patch(default_log(log_field))]
    struct Config {
        field_int: usize,
        field_string: String,
    }
    let mut config = Config::default();
    config.apply(ConfigPatch { field_int: Some(1), field_string: None });
    // log_field("field_int") is called automatically
}

§Field attributes

§#[patch(skip)]

If you want certain fields to be unpatchable, you can let the derive macro skip certain fields when creating the patch struct

#[derive(Patch)]
struct Item {
    #[patch(skip)]
    id: String,
    data: String,
}

// Generated struct
// struct ItemPatch {
//     data: Option<String>,
// }

§#[patch(skip_wrap)]

Keep the field type as-is in the generated patch struct (no extra Option wrapping). This is useful for fields that are already Option<...>, typically Option<Vec<_>>, where the default double-Option in the patch is unwanted. With skip_wrap, None in the patch means “no change” and Some(v) sets the field to Some(v) (including Some(vec![]) to clear the vector). Cannot be combined with empty_value.

#[derive(Default, Patch)]
struct Item {
    #[patch(skip_wrap)]
    tags: Option<Vec<String>>,
}

// Generated struct
// struct ItemPatch {
//     tags: Option<Vec<String>>, // not wrapped again
// }

let mut item = Item { tags: Some(vec!["a".into()]) };

// `None` in the patch keeps the field unchanged.
item.apply(ItemPatch { tags: None });
assert_eq!(item.tags, Some(vec!["a".into()]));

// `Some(vec![])` still applies and clears the list.
item.apply(ItemPatch { tags: Some(vec![]) });
assert_eq!(item.tags, Some(vec![]));

Required Methods§

Source

fn apply(&mut self, patch: P)

Apply a patch

Source

fn into_patch(self) -> P

Returns a patch that when applied turns any struct of the same type into Self

Source

fn into_patch_by_diff(self, previous_struct: Self) -> P

Returns a patch that when applied turns previous_struct into Self

Source

fn new_empty_patch() -> P

Get an empty patch instance

Provided Methods§

Source

fn apply_with_log<F: FnMut(&str)>(&mut self, patch: P, _log: F)

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§