Function/type registration at startup

Hey all,

I'm trying to create a global registry of types and associated functionality generated on program startup. For my use case, this sort of registry has a lot of advantages over maintaining a function with some long list of calls (I've tried both ways in the past with C).

In C, I've done this with attribute((constructor)) for the desired types which would populate some hash table with the required metadata, and that worked fairly well. In Rust, I can use the ctor crate, but from the documentation documentation that doesn't appear to play very well with using anything from stdlib. I don't want to play with fire and use the allocator beforehand if it may not be in a good state. Instead, I keep a massive static array and write constructor function pointers into it so I can later at runtime generate the actual data.

Is there a better way to do this in rust? Can I assume that jemalloc has been properly initialized when static constructors run? This current method is opaque enough where I have to wrap it with a fairly gnarly macro to keep it sensible to users

If you're just trying to associate methods with a type, why not just impl them? As for continuing what you were saying, rust has doesn't really have a constructor method built into types, it lets creators decide on a function to build the type, so you end up with something like so:

struct MyStruct { /**/ }
impl MyStruct {
    pub fn new() -> Self {
        MyStruct { /**/ }
    }
}

In which case you might store fn new. But then again, if you're just looking to save associated functions (Methods), then the type for that could be either:

let once: FnOnce(MyStruct) -> ();
let multiple: Fn(&MyStruct) -> ();
let fn_pointer = fn(&MyStruct) -> ();

Note that the first two are trait objects, and therefore unsized, and the last one is sized because it is a function pointer

Thanks for the reply! I need to use a registry because I need to do this lookup dynamically - basically, I need to assemble some actors based on some user-supplied configuration at runtime, type check the setup, allocate memory properly, etc. Saving fn new is basically what I’m doing. I don’t actually need these functions just metadata about the various types.

I also don’t want to maintain a “library init” function which has to know about all actor types.

However, I don’t want to rely on these types being constructive without stdlib available and use the functions as a way to delay creation.

I’m very familiar with rust’s initialization methods and would just use plain imply and news if I could, trust me. In general, I don’t like these roundabout coordinations and was hoping there is a better mechanism for doing this sort of thing

1 Like

So you're doing something like this, or perhaps with a different model of flow, but similar idea, where you take a user input and create a value depending on that?

It’s a little similar, but I also don’t know the types or really any information about them ahead of time, and in fact, probably won’t be able to link all of them into the same binary not to mention building them together.

Basically, somebody tells this library what types to instantiate via some runtime config, and if the library has beeen made aware of them somehow, it allocates space, runs their initialization code, assembled the actual sort of program the user desired. The total set of types is not known to the library at compile time and some types cannot coexist in the same program for reasons.

That’s what I solved with the module load-time code (in rust, running before and after main). Types register themselves at that point so the library can do lookup without knowing about the types ahead of time

I believe you can generate such a function either from build.rs or using procedure macro with some effort but nothing very hard.

I realize now this is might be impossible, except through build.rs, since the types will be very distributed code wise and strictly cannot be in the same codebases, or even all brought in as dependencies.

If this could be built in an incremental fashion that macro solution would be preferred to a registry

1 Like

Maybe this is what you are looking for

Please don't revive old threads like this.

1 Like

This is actually still quite helpful, I had some issues with the ctor crate at the time so just did manual registration. If this “just works” that’s super useful.

Actually, that particular crate wouldn't work all that well in this instance: the upstream crate must be made aware of the type ahead of time in its own source code.

Now that I look back on it, I'd say that you might want to look at something like AnyMap combined with #[ctor] functions. Or, if you wish to run the initialization code only once, then try something like an AnyMap which is full of fn() -> T and then pipe the T into a local AnyMap.


Or... on second thought it might be reasonable to use inventory here:

struct Initializer {
    f: fn(&mut AnyMap),
}

impl Initializer {
    fn for<T: Default>() -> Self {
        Self { 
            f: |map| map.insert::<T>(T::default())
        }
    }
}

inventory::collect!(Intializer);

And then in downstream crates, call

inventory::submit! {
    Initializer::for::<MyType>()
}

Thanks! I haven’t had time to look since I’ve been running errands each morning. Back then I had issues with ctor not getting reliably called, but iirc I remember there was some funkiness that I was also doing to load compiled .so files in C.

If it’s just the registry type that my crate needs to know about that’s fine, it’s for a pseudo-scripting tool so everything is already type erased.

You might try this one: