您如何在Rust的编译时间动态生成结构?

发布于 2025-02-12 07:37:36 字数 390 浏览 2 评论 0原文

我有以下内容:

struct Health {
  health: f32,
}

struct Position {
  position: Vec2,
}

struct Collections {
  healths: Vec<Health>,
  positions: Vec<Position>,
}

我想自动生成collections struct struct;我在想使用宏?

我认为也许我可以用自定义属性标记要包含的每个结构,然后具有构建collections struct的宏。

我该怎么做?

I have the following:

struct Health {
  health: f32,
}

struct Position {
  position: Vec2,
}

struct Collections {
  healths: Vec<Health>,
  positions: Vec<Position>,
}

I would like to generate the Collections struct automatically; I am thinking using a macro?

I thought perhaps I could mark each struct I want to include with a custom attribute and then have a macro which builds the Collections struct.

How could I do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梓梦 2025-02-19 07:37:36

为了能够执行诸如自定义属性之类的事情,您需要编写proc_macro,它几乎可以用代码来完成您需要的任何事情。

对于更简单的解决方案,您可以尝试使用普通的macro_rules。为此,您需要将您的类型定义包装到进行解析的宏中,并返回类型定义加上所需的额外代码,在您的情况下,container类。

类似的事情:

macro_rules! collectables {
    (
        $(
            #[collection=$fname:ident]
            $(#[$attr:meta])?
            $vis:vis struct $name:ident $def:tt
        )*
    ) => {
        // The struct definitions
        $(
            $(#[$attr])?
            $vis struct $name $def
        )*
        
        // The container
        #[derive(Default, Debug)]
        pub struct Collections {
          $(
            $fname: Vec<$name>,
          )*
        }
    };
}

现在您可以使用宏来构建原始代码( Playground ):

collectables!{
    #[collection=healths]
    #[derive(Debug)]
    struct Health {
      health: f32,
    }
    
    #[collection=positions]
    #[derive(Debug)]
    struct Position {
      position: (f32, f32),
    }
}

请注意,作为书面的#[Collection = XXX]属性是强制性的,并且必须是每个结构定义中的第一个。

To be able to do something like custom attributes you need to write a proc_macro, that can do almost anything you need with your code.

For a simpler solution you may try with a normal macro_rules. For that you will need to enclose your type definitions into a macro that does the parsing, and emits back the type definition plus the extra code you need, in your case the Container class.

Something like this:

macro_rules! collectables {
    (
        $(
            #[collection=$fname:ident]
            $(#[$attr:meta])?
            $vis:vis struct $name:ident $def:tt
        )*
    ) => {
        // The struct definitions
        $(
            $(#[$attr])?
            $vis struct $name $def
        )*
        
        // The container
        #[derive(Default, Debug)]
        pub struct Collections {
          $(
            $fname: Vec<$name>,
          )*
        }
    };
}

Now you can use the macro to build your original code (playground):

collectables!{
    #[collection=healths]
    #[derive(Debug)]
    struct Health {
      health: f32,
    }
    
    #[collection=positions]
    #[derive(Debug)]
    struct Position {
      position: (f32, f32),
    }
}

Note that as written the #[collection=xxx] attribute is mandatory and must be the first in every struct definition.

柏拉图鍀咏恒 2025-02-19 07:37:36

因此,我设法使用proc_macro解决了这个问题。将包含在最终存储 struct中的每个结构都用组件衍生属性标记。然后,使用存储结构将使用storege!()宏构建。

use lazy_static::lazy_static;
use proc_macro::TokenStream;
use quote::quote;
use std::sync::Mutex;
use syn::{parse_macro_input, parse_str, DeriveInput, ExprType};

lazy_static! {
    static ref COMPONENTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}

#[proc_macro_derive(Component)]
pub fn component(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput); ‣DeriveInput
    let ident = input.ident; ‣Ident
    COMPONENTS.lock().unwrap().push(ident.to_string());
    let expanded = quote! { ‣TokenStream
        impl component::Component for #ident {}
    };
    TokenStream::from(expanded)
}

#[proc_macro]
pub fn storage(_input: TokenStream) -> TokenStream {
    println!("Building Storage with: {:?}", COMPONENTS.lock().unwrap());
    let mut fields = Vec::new(); ‣Vec<ExprType>
    for type_name in COMPONENTS.lock().unwrap().iter() { ‣&String
        let field = parse_str::<ExprType>( ‣ExprType
            format!("{}s: Vec<{}>", type_name.to_lowercase(), type_name).as_str(),
        ) ‣Result<ExprType, Error>
        .expect("Could not parse component field type");
        fields.push(field);
    }
    let expanded = quote! { ‣TokenStream
        #[derive(Serialize, Deserialize, Debug, Default)]
        struct Storage {
            #(#fields),*
        }
    };
    TokenStream::from(expanded)
}
#[derive(Debug, Serialize, Deserialize, Component)]
struct Health {
    health: f32,
}
#[derive(Debug, Serialize, Deserialize, Component)]
pub struct Age {
    pub age: u64,
}
storage!();

So I managed to solve this problem using a proc_macro. Each struct which is to be included in the final Storage struct is marked with the Component derive attribute. The Storage struct is then built with the storage!() macro.

use lazy_static::lazy_static;
use proc_macro::TokenStream;
use quote::quote;
use std::sync::Mutex;
use syn::{parse_macro_input, parse_str, DeriveInput, ExprType};

lazy_static! {
    static ref COMPONENTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}

#[proc_macro_derive(Component)]
pub fn component(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput); ‣DeriveInput
    let ident = input.ident; ‣Ident
    COMPONENTS.lock().unwrap().push(ident.to_string());
    let expanded = quote! { ‣TokenStream
        impl component::Component for #ident {}
    };
    TokenStream::from(expanded)
}

#[proc_macro]
pub fn storage(_input: TokenStream) -> TokenStream {
    println!("Building Storage with: {:?}", COMPONENTS.lock().unwrap());
    let mut fields = Vec::new(); ‣Vec<ExprType>
    for type_name in COMPONENTS.lock().unwrap().iter() { ‣&String
        let field = parse_str::<ExprType>( ‣ExprType
            format!("{}s: Vec<{}>", type_name.to_lowercase(), type_name).as_str(),
        ) ‣Result<ExprType, Error>
        .expect("Could not parse component field type");
        fields.push(field);
    }
    let expanded = quote! { ‣TokenStream
        #[derive(Serialize, Deserialize, Debug, Default)]
        struct Storage {
            #(#fields),*
        }
    };
    TokenStream::from(expanded)
}
#[derive(Debug, Serialize, Deserialize, Component)]
struct Health {
    health: f32,
}
#[derive(Debug, Serialize, Deserialize, Component)]
pub struct Age {
    pub age: u64,
}
storage!();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文