Rust宏(proc-macro)库partially_derive的使用:实现部分派生(derive)功能的自定义过程宏扩展

Rust宏(proc-macro)库partially_derive的使用:实现部分派生(derive)功能的自定义过程宏扩展

简介

partially提供了一个可配置的派生宏,它会生成另一个结构体,其中所有字段都包装在Option<T>中,并实现Partial特性以允许将有值的字段有条件地应用到基础结构体。

partially_derive(或启用derive特性的partially)支持自动生成每个字段都包装在Option<T>中的镜像结构体,并生成一个Partial实现,允许将镜像结构体中的Some字段应用到基础结构体。

使用示例

使用派生宏

// `partially`安装时启用`derive`特性
use partially::Partial;

// 定义一个基础结构体,并使用`Partial`派生宏
#[derive(Partial)]
// 进一步指示宏在生成的结构体上派生`Default`
#[partially(derive(Default))]
struct Data {
    // 由于没有指定字段选项,这个字段将被映射到生成结构体中的`Option<String>`
    value: String,
}

// 示例用法
fn main() {
    // 由于我们为生成的结构体派生了Default,我们可以使用它来获取一个填充了`None`的部分结构体
    let empty_partial = PartialData::default();

    // 当然,我们也可以自己指定值
    let full_partial = PartialData {
        value: Some("modified".to_string()),
    };

    // 定义一个我们将操作的"基础"结构体
    let mut full = Data {
        value: "initial".to_string(),
    };

    // 应用空的部分结构体(注意返回`false`,表示没有应用任何内容)
    assert!(!full.apply_some(empty_partial));

    // 注意应用空部分结构体没有效果
    assert_eq!(full.value, "initial".to_string());

    // 应用完整部分结构体(注意返回`true`,表示应用了某些内容)
    assert!(full.apply_some(full_partial));

    // 注意应用完整部分结构体修改了值
    assert_eq!(full.value, "modified".to_string());
}

不使用派生宏

use partially::Partial;

struct Base {
    value: String,
}

#[derive(Default)]
struct PartialBase {
    value: Option<String>,
}

impl Partial for Base {
    type Item = PartialBase;

    #[allow(clippy::useless_conversion)]
    fn apply_some(&mut self, partial: Self::Item) -> bool {
        let will_apply_some = partial.value.is_some();

        if let Some(value) = partial.value {
            self.value = value.into();
        }

        will_apply_some
    }
}

fn main() {
    let empty_partial = PartialBase::default();
    let full_partial = PartialBase {
        value: Some("modified".to_string()),
    };

    let mut data = Base {
        value: "initial".to_string(),
    };

    assert!(!data.apply_some(empty_partial));

    assert_eq!(data.value, "initial".to_string());

    assert!(data.apply_some(full_partial));

    assert_eq!(data.value, "modified".to_string())
}

结构体选项

derive

用法示例:#[partially(derive(Debug, Default))]

指示宏在生成的结构体上生成#[derive(...)]属性。

注意:当此选项与skip_attributes选项一起使用时,derive属性仍将添加到生成的结构体中。

rename

用法示例:#[partially(rename = "MyGeneratedStruct")]

指示宏使用给定的标识符作为生成的结构体名称。默认情况下,使用PartialBaseStructName,其中BaseStructName是原始结构体的名称。

attribute

用法示例:#[partially(attribute(serde(rename_all = "PascalCase")))]

指示宏向生成的结构体添加额外的属性。默认情况下,基础结构体上定义的属性会转发到生成的结构体,除非存在skip_attributes选项。

skip_attributes

用法示例:#[partially(skip_attributes)]

指示宏跳过将属性从原始结构体转发到生成的结构体。默认情况下,基础结构体上的所有属性都会添加到生成的结构体中。

注意:当此选项与derive选项一起使用时,derive属性仍将添加到生成的结构体中。

注意:当此选项与`attribute》选项一起使用时,指定的属性仍将添加到生成的结构体中。

crate

用法示例:#[partially(crate = "my_partially_crate")]

指示宏对Partial特性实现使用不同的基础路径。默认情况下使用partially。如果你fork了partially crate,这会很有用。

字段选项

rename

用法示例:#[partially(rename = "new_field_name")]

指示宏为生成的字段使用给定的标识符。默认情况下使用与基础结构体相同的名称。

omit

用法示例:#[partially(omit)]

指示宏从生成的结构体中省略该字段。默认情况下不省略任何字段。

transparent

用法示例:#[partially(transparent)]

指示宏跳过将生成的字段包装在Option<T>中,而是透明地将字段类型镜像到生成的结构体中。

as_type

用法示例:#[partially(as_type = "Option<f32>")]

指示宏在生成字段时使用提供的类型而不是Option<T>。请注意,提供的类型将按原样使用,因此如果你期望Option<T>值,则需要手动指定。

注意:使用as_type时,给定类型必须Into<BaseType>,其中BaseType是原始字段类型。这对于Partial特性实现是必需的。

完整示例代码

use partially::Partial;

// 定义一个更复杂的基础结构体
#[derive(Partial, Debug)]
#[partially(derive(Debug, Default, Clone))]
#[partially(rename = "MyPartialData")]
#[partially(attribute(serde(rename_all = "camelCase")))]
struct UserData {
    id: u64,
    #[partially(rename = "user_name")]
    name: String,
    #[partially(omit)]
    password: String,
    #[partially(transparent)]
    is_active: bool,
    #[partially(as_type = "Option<f32>")]
    score: f64,
}

fn main() {
    // 使用默认值创建部分结构体
    let mut partial = MyPartialData::default();
    println!("Default partial: {:?}", partial);

    // 设置部分值
    partial.user_name = Some("Alice".to_string());
    partial.is_active = true; // 透明字段,不需要Option
    partial.score = Some(85.5); // 使用as_type指定的类型

    // 创建基础结构体
    let mut user = UserData {
        id: 1,
        name: "Bob".to_string(),
        password: "secret".to_string(),
        is_active: false,
        score: 70.0,
    };

    println!("Before apply: {:?}", user);

    // 应用部分更新
    let changes_applied = user.apply_some(partial.clone());
    println!("Changes applied: {}", changes_applied);
    println!("After apply: {:?}", user);

    // 检查透明字段
    assert!(user.is_active);
    // 检查重命名字段
    assert_eq!(user.name, "Alice");
    // 检查as_type字段
    assert_eq!(user.score, 85.5);
    // 检查被忽略的字段
    assert_eq!(user.password, "secret");
}

这个完整示例展示了:

  1. 使用rename选项自定义生成的结构体名称
  2. 使用attribute选项添加serde属性
  3. 使用omit选项跳过某些字段
  4. 使用transparent选项使字段不包装在Option中
  5. 使用as_type选项自定义字段类型
  6. 展示了如何应用部分更新并验证结果

1 回复

Rust宏库partially_derive使用指南

partially_derive是一个Rust过程宏库,允许你为结构体或枚举实现部分派生(derive)功能,而不是强制要求实现所有trait方法。

主要功能

  • 选择性派生:只实现你需要的trait方法
  • 自定义实现:可以手动实现部分方法,让宏生成其余方法
  • 组合派生:结合自动生成和手动实现

使用方法

基本安装

首先在Cargo.toml中添加依赖:

[dependencies]
partially_derive = "0.1"

基本示例

use partially_derive::PartialDerive;

#[derive(PartialDerive)]
#[derive_partial(Default, Debug)]
struct MyStruct {
    field1: i32,
    field2: String,
}

// 相当于实现了Debug和Default trait
// 但没有实现其他常见的派生trait如Clone, Copy等

选择性实现方法

#[derive(PartialDerive)]
#[derive_partial(Eq, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

// 手动实现PartialOrd
impl PartialOrd for Point {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

// 然后可以继续派生Ord
#[derive_partial(Ord)]
impl Point;

枚举示例

#[derive(PartialDerive)]
#[derive_partial(Debug, Clone)]
enum MyEnum {
    Variant1,
    Variant2(String),
    Variant3 { field: i32 },
}

高级用法:自定义属性

#[derive(PartialDerive)]
#[derive_partial(Serialize, Deserialize)]
#[serde(crate = "serde::")]
struct Config {
    #[serde(rename = "name")]
    username: String,
    #[serde(default)]
    timeout: u32,
}

完整示例Demo

下面是一个完整的示例,展示了如何使用partially_derive进行选择性派生和组合派生:

use partially_derive::PartialDerive;
use std::cmp::Ordering;

// 基本结构体示例
#[derive(PartialDerive)]
#[derive_partial(Debug, Clone)] // 只派生Debug和Clone
struct User {
    id: u64,
    name: String,
    age: u8,
}

// 选择性实现示例
#[derive(PartialDerive)]
#[derive_partial(PartialEq, Eq)] // 先实现PartialEq和Eq
struct Product {
    id: u32,
    price: f64,
    in_stock: bool,
}

// 手动实现PartialOrd
impl PartialOrd for Product {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.price.partial_cmp(&other.price)
    }
}

// 然后派生Ord
#[derive_partial(Ord)]
impl Product;

// 枚举示例
#[derive(PartialDerive)]
#[derive_partial(Debug, Clone, PartialEq)]
enum PaymentMethod {
    CreditCard { number: String, expiry: String },
    Paypal(String),
    Cash,
}

// 带自定义属性的高级示例
#[derive(PartialDerive)]
#[derive_partial(Serialize, Deserialize)]
#[serde(crate = "serde::")]
struct AppConfig {
    #[serde(rename = "app_name")]
    name: String,
    #[serde(default = "default_port")]
    port: u16,
}

fn default_port() -> u16 {
    8080
}

fn main() {
    // 使用派生的Debug trait
    let user = User {
        id: 1,
        name: "Alice".to_string(),
        age: 30,
    };
    println!("{:?}", user);
    
    // 使用派生的Clone trait
    let user2 = user.clone();
    
    // 使用派生的比较trait
    let p1 = Product { id: 1, price: 9.99, in_stock: true };
    let p2 = Product { id: 2, price: 19.99, in_stock: false };
    assert!(p1 < p2);
}

适用场景

  1. 当你只需要某些trait而不想派生所有常见trait时
  2. 当你想结合自动生成和手动实现时
  3. 当某些trait需要自定义属性配置时

注意事项

  • 确保你手动实现的方法与自动生成的不会冲突
  • 某些trait之间有依赖关系(如Eq需要PartialEq),需要按正确顺序派生
  • 检查生成的代码是否符合预期,可以使用cargo expand查看宏展开结果

这个库特别适合那些需要精细控制派生trait实现的场景,避免了全量派生可能带来的不必要代码生成。

回到顶部