You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写泛型函数或宏从Rust结构体中提取指定字段?

问题描述

我定义了多种资产类型的结构体,示例如下:

#[derive(Debug, Deserialize, Serialize)]
struct Vehicle {
    short: String,
    name: String,
    number_plate: String,
    purchase_cost: u32,
    purchase_date: NaiveDate,
    charge_minute: f32,
    charge_km: f32,
}

这些结构体字段各不相同,但都包含short和name字段(均为String类型)。我需要从这类结构体的集合中提取出(short, name)组成的Vec,针对Vehicle编写的函数可以正常工作:

fn vehicle_pairs(vs: &Vec<Vehicle>) -> Vec<(String, String)> {
    vs.iter()
        .map(|v| (v.short.to_string(), v.name.to_string()))
        .collect::<Vec<_>>()
}

但尝试用宏实现泛化时却失败了,我的宏代码如下:

macro_rules! pairs {
    ($T:ty) => {
        fn get_pairs<T>(xs: &Vec<T>) -> Vec<(String, String)> {
            xs.iter()
                .map(|x| (x.short.to_string(), x.name.to_string()))
                .collect::<Vec<_>>()
        }
    }
}

pairs!(Vehicle);

请问如何编写能从包含short和name字段的结构体中提取这两个字段的泛型函数?

解决方案

方法一:使用特质(Trait)实现泛型(推荐)

Rust中优先通过特质抽象行为,而非直接依赖字段。我们可以定义一个特质来规范short和name的访问逻辑,再为目标结构体实现该特质,最后编写泛型函数。

  1. 定义特质:
trait HasShortName {
    fn short(&self) -> &str;
    fn name(&self) -> &str;
}
  1. 为结构体实现特质:
impl HasShortName for Vehicle {
    fn short(&self) -> &str {
        &self.short
    }

    fn name(&self) -> &str {
        &self.name
    }
}

// 其他带short/name字段的结构体,按同样方式实现HasShortName即可
  1. 编写泛型函数:
fn get_pairs<T: HasShortName>(xs: &[T]) -> Vec<(String, String)> {
    xs.iter()
        .map(|x| (x.short().to_string(), x.name().to_string()))
        .collect()
}

这里用&[T]代替&Vec<T>更灵活,所有切片类型(包括Vec的引用)都能传入。

方法二:使用宏生成专属函数

如果偏好宏实现,之前的宏错误在于未将传入类型与函数逻辑关联。正确的宏可以为每个目标类型生成专属的提取函数:

macro_rules! make_pairs_fn {
    ($fn_name:ident, $T:ty) => {
        fn $fn_name(xs: &Vec<$T>) -> Vec<(String, String)> {
            xs.iter()
                .map(|x| (x.short.to_string(), x.name.to_string()))
                .collect()
        }
    };
}

// 为Vehicle生成提取函数
make_pairs_fn!(vehicle_pairs, Vehicle);
// 为其他结构体生成函数,比如Asset
// make_pairs_fn!(asset_pairs, Asset);

也可以用宏自动生成特质实现,减少重复代码:

macro_rules! impl_has_short_name {
    ($T:ty) => {
        impl HasShortName for $T {
            fn short(&self) -> &str {
                &self.short
            }

            fn name(&self) -> &str {
                &self.name
            }
        }
    };
}

// 为Vehicle自动实现特质
impl_has_short_name!(Vehicle);
// 其他结构体同理
impl_has_short_name!(Asset);

方法三:自定义派生宏(进阶)

如果有大量结构体需要处理,可以自定义派生宏,让结构体通过#[derive(HasShortName)]自动实现特质。

  1. 在Cargo.toml中添加依赖:
[dependencies]
syn = "2.0"
quote = "1.0"

[lib]
proc-macro = true
  1. 编写proc-macro代码:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HasShortName)]
pub fn derive_has_short_name(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let expanded = quote! {
        impl HasShortName for #name {
            fn short(&self) -> &str {
                &self.short
            }

            fn name(&self) -> &str {
                &self.name
            }
        }
    };

    TokenStream::from(expanded)
}
  1. 在主项目中使用:
use your_crate_name::HasShortName;

#[derive(Debug, Deserialize, Serialize, HasShortName)]
struct Vehicle {
    short: String,
    name: String,
    // 其他字段...
}

内容的提问来源于stack exchange,提问作者AlexKing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 10:33:24