如何编写泛型函数或宏从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的访问逻辑,再为目标结构体实现该特质,最后编写泛型函数。
- 定义特质:
trait HasShortName { fn short(&self) -> &str; fn name(&self) -> &str; }
- 为结构体实现特质:
impl HasShortName for Vehicle { fn short(&self) -> &str { &self.short } fn name(&self) -> &str { &self.name } } // 其他带short/name字段的结构体,按同样方式实现HasShortName即可
- 编写泛型函数:
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)]自动实现特质。
- 在Cargo.toml中添加依赖:
[dependencies] syn = "2.0" quote = "1.0" [lib] proc-macro = true
- 编写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) }
- 在主项目中使用:
use your_crate_name::HasShortName; #[derive(Debug, Deserialize, Serialize, HasShortName)] struct Vehicle { short: String, name: String, // 其他字段... }
内容的提问来源于stack exchange,提问作者AlexKing
相关产品推荐
相关产品推荐

