Rust插件库partially的使用:高效部分数据加载与处理工具

Rust插件库partially的使用:高效部分数据加载与处理工具

partially提供了一个可配置的derive宏,它会生成另一个结构体,该结构体具有相同的字段但被包装在Option<T>中,并实现了Partial特性,允许将有条件地将生成结构体的字段应用到基础结构体字段上。

partially提供了Partial特性,允许将另一个结构体的值应用到self上,目的是另一个结构体镜像self的字段,但包装在Option<T>中。

此外,partially_derive(或启用derive特性的partially)支持自动生成一个每个字段都包装在Option<T>中的镜像结构体,并且生成一个Partial实现,允许将镜像结构体的Some字段应用到基础结构体上。我预计大多数人会对使用派生宏最感兴趣。

使用方式

使用derive宏

// 使用`derive`特性安装`partially`
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());
}

不使用derive宏

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特性实现所必需的。

完整示例代码

下面是一个更完整的示例,展示了如何使用partially来部分更新一个用户配置文件:

use partially::Partial;

// 定义一个用户配置文件结构体
#[derive(Partial, Debug)]
#[partially(derive(Default))]
struct UserProfile {
    username: String,
    email: String,
    age: u32,
    is_verified: bool,
}

fn main() {
    // 创建一个完整的用户配置文件
    let mut profile = UserProfile {
        username: "john_doe".to_string(),
        email: "john@example.com".to_string(),
        age: 30,
        is_verified: false,
    };

    println!("Original profile: {:?}", profile);

    // 创建一个部分更新,只修改email和验证状态
    let partial update = PartialUserProfile {
        email: Some("john.new@example.com".to_string()),
        is_verified: Some(true),
        ..Default::default()
    };

    // 应用部分更新
    profile.apply_some(partial_update);

    println!("Updated profile: {:?}", profile);

    // 创建一个只更新年龄的部分更新
    let age_update = PartialUserProfile {
        age: Some(31),
        ..Default::default()
    };

    // 应用年龄更新
    profile.apply_some(age_update);

    println!("After age update: {:?}", profile);
}

这个示例展示了如何:

  1. 定义一个带有Partial特性的结构体
  2. 创建部分更新结构体(自动生成的PartialUserProfile)
  3. 选择性地更新结构体的某些字段
  4. 多次应用部分更新

输出将显示原始配置文件,然后显示每次部分更新后的状态。


1 回复

Rust插件库partially的使用:高效部分数据加载与处理工具

简介

partially是一个Rust库,专注于高效地部分加载和处理大型数据结构。它特别适合处理需要从存储中加载的大型对象,但只需要访问其中一小部分数据的场景。

主要特性

  • 延迟加载:仅在需要时加载数据的特定部分
  • 内存高效:避免加载整个数据结构到内存
  • 类型安全:保持Rust的类型安全性
  • 易用API:提供简洁的接口与现有代码集成

使用方法

添加依赖

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

[dependencies]
partially = "0.4"

基本使用示例

use partially::Partial;

#[derive(Partial)]
struct LargeData {
    id: u64,
    metadata: String,
    // 假设这是很大的数据,我们不总是需要
    big_data: Vec<u8>,
    // 另一个大数据字段
    additional_data: Vec<f64>,
}

fn main() {
    // 创建部分加载对象
    let partial_data = LargeData::partial()
        .with_id(42)
        .with_metadata("Sample data".to_string())
        .without_big_data()
        .without_additional_data();
    
    // 使用部分数据
    println!("ID: {}", partial_data.id);
    println!("Metadata: {}", partial_data.metadata);
    
    // 尝试访问未加载的字段会返回None
    println!("Big data length: {:?}", partial_data.big_data);
}

高级用法:条件加载

use partially::{Partial, PartialField};

fn process_data(load_full: bool) {
    let mut builder = LargeData::partial()
        .with_id(1)
        .with_metadata("Test".to_string());
    
    if load_full {
        builder = builder
            .with_big_data(vec![1, 2, 3])
            .with_additional_data(vec![1.0, 2.0]);
    }
    
    let data = builder.build();
    
    // 处理数据...
}

序列化/反序列化支持

partially可以与serde配合使用:

use partially::Partial;
use serde::{Serialize, Deserialize};

#[derive(Partial, Serialize, Deserialize)]
struct SerializableData {
    id: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    optional_field: Option<String>,
}

fn serialize_partial() {
    let data = SerializableData::partial()
        .with_id(10)
        .without_optional_field();
    
    let json = serde_json::to_string(&data).unwrap();
    println!("{}", json); // 输出: {"id":10}
}

完整示例demo

use partially::{Partial, PartialField};
use serde::{Serialize, Deserialize};

// 定义一个大型数据结构
#[derive(Partial, Debug, Serialize, Deserialize)]
struct UserProfile {
    user_id: u64,
    username: String,
    // 可能很大的数据字段
    profile_picture: Vec<u8>,
    // 另一个大数据字段
    activity_history: Vec<String>,
    // 可选字段
    preferences: Option<String>,
}

fn main() {
    // 场景1:只需要基本用户信息
    let basic_profile = UserProfile::partial()
        .with_user_id(101)
        .with_username("rustfan".to_string())
        .without_profile_picture()
        .without_activity_history()
        .without_preferences();
    
    println!("基本用户信息:");
    println!("ID: {}", basic_profile.user_id);
    println!("用户名: {}", basic_profile.username);
    println!("头像数据: {:?}", basic_profile.profile_picture); // 输出None
    
    // 场景2:根据条件加载完整或部分数据
    let load_full_data = false; // 模拟条件
    let mut profile_builder = UserProfile::partial()
        .with_user_id(102)
        .with_username("partialuser".to_string());
    
    if load_full_data {
        profile_builder = profile_builder
            .with_profile_picture(vec![0x89, 0x50, 0x4E, 0x47]) // 模拟PNG头
            .with_activity_history(vec![
                "2023-01-01: 登录".to_string(),
                "2023-01-02: 发表帖子".to_string()
            ])
            .with_preferences("dark theme".to_string());
    }
    
    let conditional_profile = profile_builder.build();
    println!("\n条件加载的用户信息:");
    println!("{:#?}", conditional_profile);
    
    // 场景3:序列化部分数据
    #[derive(Partial, Serialize, Deserialize)]
    struct Config {
        id: u32,
        #[serde(skip_serializing_if = "Option::is_none")]
        description: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        settings: Option<Vec<String>>,
    }
    
    let minimal_config = Config::partial()
        .with_id(1)
        .without_description()
        .without_settings();
    
    let json = serde_json::to_string(&minimal_config).unwrap();
    println!("\n最小化配置JSON:");
    println!("{}", json); // 输出: {"id":1}
}

实际应用场景

  1. Web API响应:只返回客户端需要的字段
  2. 数据库查询:避免SELECT *,只获取必要列
  3. 大型文件处理:只加载文件的特定部分
  4. 配置管理:部分覆盖配置而不加载全部

性能建议

  • 对频繁访问的字段使用with_方法预先加载
  • 对不常用的字段使用without_方法跳过加载
  • 考虑使用PartialField枚举来显式处理可能缺失的字段

partially库通过提供灵活的部分数据加载能力,可以显著提高处理大型数据结构时的内存效率和性能。

回到顶部