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");
}
这个完整示例展示了:
- 使用
rename
选项自定义生成的结构体名称 - 使用
attribute
选项添加serde属性 - 使用
omit
选项跳过某些字段 - 使用
transparent
选项使字段不包装在Option中 - 使用
as_type
选项自定义字段类型 - 展示了如何应用部分更新并验证结果
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);
}
适用场景
- 当你只需要某些trait而不想派生所有常见trait时
- 当你想结合自动生成和手动实现时
- 当某些trait需要自定义属性配置时
注意事项
- 确保你手动实现的方法与自动生成的不会冲突
- 某些trait之间有依赖关系(如Eq需要PartialEq),需要按正确顺序派生
- 检查生成的代码是否符合预期,可以使用
cargo expand
查看宏展开结果
这个库特别适合那些需要精细控制派生trait实现的场景,避免了全量派生可能带来的不必要代码生成。