Gather macro invocations? (e.g. class factory, 'roll one big enum',..)


#1

Would there be demand for a feature like this - or an existing way to do it:

imagine macros for declaring classes in a system, (e.g. one class per soucefile) then something to gather!(other_macro) their invocations in one location (e.g. to roll code to list them in a UI, iterate them coherently at runtime , whatever you need). You could seamlessly switch from ‘sort by type’ to ‘sort by function’ approaches.

example :-

// many of these scattered between source-files
// 'declare_class!{} in this example is empty

// foo.rs
declare_class!{
    Foo { /* foo fields*/ }, 
    render{println!("i am a foo")}, 
    update{}, 
    ...
}  
// bar.rs
declare_class!{
    Bar { /* bar fields */ } ,
    render{println!("i am a bar")}, 
    update{},
    ... 
}  
// baz.rs
declare_class!{ Baz ... }

// then in one source file:-
// classes.rs
generate_class_list!{   gather!(declare_class)   }  <<<<< PROPOSED FEATURE

//gather! instantiates itself as follows:-

generate_class_list!{
     {    Foo {/*fields go here*/}   
          Render{println!("i am a foo")} Update{} 
     }
     {    Bar {    }       
          Render{println!("i am a bar")} 
          Update{} 
     }
 } 

// uber-macro 'generate_class_list'
// actually rolls everything.. a list of all the classes in a big enum for 'matching' against,
// plain structs for all their data,  a list of each type stored coherently in a homogenous vector,
// result might be as below,
// but you could easily switch between 'struct of arrays per type',  'array of trait-objets', 'one big enum'


struct Foo { 
     /* foo fields */
}
impl Foo { 
    fn render(&self) {  println!("i am a foo") }
    fn update(&self) {  .. } 
    ...
}
struct Bar {
    /* bar fields */
}
impl Bar {
    fn render(&self) {  println!("i am a bar") } 
    fn update(&self) {   } 
    ...
}
...

struct World {
    foo:vector<Foo>
    bar:vector<Bar>
    baz:vector<Baz>
}
impl World {
    fn update() {
        for x in self.foo.iter() {   x.update() }
        for x in self.bar.iter() {  x.update() }
        for x in self.baz.iter() {  x.update() }
    }
    fn render() {
        for x in self.foo.iter() {   x.render() }
        for x in self.bar.iter() {  x.render() }
        for x in self.baz.iter() {  x.render() }
    }       
}

// but you could easily also have rolled it..
enum UberEntity {
    Foo { /* foo fields */
    Bar { /* bar fields */
}
impl UberEntity {
    fn render(&self) {
        match self {
            Foo{..}=> {
               println! ( "i am a foo!" );
            }
            Foo{..}=> {
               println! ( "i am a bar!" );
            }
        }
    }
}

// or rolled trait objects for a more OOP-like approach ..

Of course they could be nested, e.g. ‘declare event types’, ‘roll an event dispatcher per entity’


#2

I’d say #[test] as a concept is already similar in functionality, so I would say it is generally proven to be useful already. Another use-case I could see is for external API generation (like integration into a dynamic language).


#3

I’ve not looked into ‘procedural macros’ ( supposedly they’re like compiler plugins?), maybe this could already by done with one of those, and you’d write #[gather_into(class_list)] infront of normal looking ‘struct’ /‘impl’ blocks perhaps…

in some ways I would prefer that to avoid deviating too far into custom syntax (‘writing your whole program as macros…’); The goal here is to be able to mix or change means of polymorphism/dispatch, and to organise code differently (deferring the decision between open & closed types…).

but anything thats already baked into the compiler would see more use IMO

Maybe a ‘gather’ procedural macro could list all the idents under a given macro name,

#[gather_into(class_list)]
struct Foo { .. }

#[gather_into(class_list)]
struct bar { ... }

// generates..
class_list!( Foo,Bar,Baz )   // .. then 'def_classes rolls what you need..