英文:
Creating a Macro in Rust to take a Struct and turning into a Tuple of its field's types in order
问题
基本上,目标是能够获取一个类似以下结构的结构体:
struct Test
{
id: i64,
name: String,
data: HashMap<String, String>,
}
并且使宏返回一个元组,其中包含字段类型,按照我们声明test结构的字段顺序,如下所示:
这样它可以在以下上下文中使用,例如在scylla::IntoTypedRows中:
rows.into_typed::<(get_field_types!(Test))>()
而不是当前的方式,即:
rows.into_typed::<(i64, String, HashMap<String, String>)>()
这样,随着结构体变得更大,不会变得繁琐或臃肿。
我遇到的问题是我能够返回Vec<&str>,但似乎无法理解如何实现上述目标。以下是我的尝试:
macro_rules! get_field_types {
($struct_name:ty) => {
{
let mut field_types = Vec::new();
let _ = <$struct_name>::default(); // 确保实例化结构体
let field_values = stringify!($struct_name {
$(field: _type,)*
..
});
let field_pairs = field_values.split(',').filter_map(|field_value| {
let field_type = field_value.split_whitespace().nth(1)?;
Some(field_type.trim_end_matches(',').trim())
});
field_types.extend(field_pairs);
field_types
}
};
}
英文:
Basically, the goal is to be able to take a struct like the following:
struct Test
{
id : i64,
name : String,
data : HashMap<String, String>,
}
And have the macro return a tuple of its field types in the order that we declared the fields of test like follows:
So it could be used in the following context for example in scylla::IntoTypedRows;
rows.into_typed::<(get_field_types!(Test))>()
instead of the current way it has to be done which is:
rows.into_typed::<(i64, String, HashMap<String, String>)>()
That way as structs get larger, it does not become tedious or bulky.
The issue I have had has been that I am able to return a Vec<&str> but can't seem to reason how to get it to do the above. Here was my attempt:
macro_rules! get_field_types {
($struct_name:ty) => {
{
let mut field_types = Vec::new();
let _ = <$struct_name>::default(); // Ensure the struct is instantiated
let field_values = stringify!($struct_name {
$(field: _type,)*
..
});
let field_pairs = field_values.split(',').filter_map(|field_value| {
let field_type = field_value.split_whitespace().nth(1)?;
Some(field_type.trim_end_matches(',').trim())
});
field_types.extend(field_pairs);
field_types
}
};
}
答案1
得分: 1
PitaJ在这里是正确的,你应该使用#[derive(FromRow)]
来做这个,然后使用rows.into_typed::<Test>()
,因为这样可以避免维护宏的麻烦,使你的结构更加灵活。
为了学习的目的,以下是如何使用声明性宏来实现这一点。虽然宏目前无法执行查找(如果它们以后能够执行查找,我会感到惊讶),但你可以在结构声明中应用它们,像这样:
use paste::paste;
macro_rules! field_tuple {
(
$vis_:vis struct $name:ident {
$($fvis_:vis $it:ident : $typ:ty, )*
}
) => {
$vis_ struct $name {
$($fvis_ $it: $typ),*
}
paste! {
$vis_ type [< $name FieldTypes >] = ($($typ),*);
}
};
}
enum Color {
Pink,
Blue,
Black,
White,
}
field_tuple!(struct Penguin {
pub height: u32,
pub(crate) weight: u8,
color: Color,
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compiles() {
let _: PenguinFieldTypes;
}
}
这使用了paste
crate 来连接结构名和 FieldTypes
以生成一个类型定义,这样你就可以用它来定义多个字段元组类型,而不会互相冲突。
英文:
PitaJ is correct here that you should be doing this with #[derive(FromRow)]
and using rows.into_typed::<Test>()
, as it saves you the hassle of maintaining a macro, and makes your struct more flexible.
In the spirit of learning, here is how you could do this with a declarative macro. While macros can't do lookups currently (and I would be surprised if they ever would be able to), you can apply them at struct declaration like so:
use paste::paste;
macro_rules! field_tuple {
(
$vis_:vis struct $name:ident {
$($fvis_:vis $it:ident : $typ:ty, )*
}
) => {
$vis_ struct $name {
$($fvis_ $it: $typ),*
}
paste! {
$vis_ type [<$name FieldTypes>] = ($($typ),*);
}
};
}
enum Color {
Pink,
Blue,
Black,
White,
}
field_tuple!(struct Penguin {
pub height: u32,
pub(crate) weight: u8,
color: Color,
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compiles() {
let _: PenguinFieldTypes;
}
}
This uses the paste
crate in order to concatenate the struct name and FieldTypes
in order to generate a type definition, so that you can use it to define multiple field-tuple types without conflicting with one another.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论