3

I apologize if I'm not asking this question properly. I feel like part of the issue is that I'm having hard time trying to idiomatically describe what I'm trying to do.

I am currently trying to figure out if I can register handler functions defined in lib.rs into a service, based only on reading a YAML manifest file.

I have a manifest file config.yaml

event: count
handler: count_handler

I have a lib.rs with the following definitions:

// This is somewhat pseudo-code (has not been tested, but gives you the idea)
fn count_handler(d: Vec<u8>) -> Vec<u8> {
    println!("count_handler was called!");
    Vec::new()
}

type HandlerFn = fn(Vec<u8>) -> Vec<u8>;

struct Service {
  handlers: HashMap<String, HandlerFn>,
}

impl Service {
   fn new() -> Self {
     let handlers: HashMap::new();
     Self { handlers }
   }

   fn register_handler(&mut self, name: String, handler: HandlerFn) {
       self.handlers.insert(name, handler);
   }

   fn start() {
       println!("I'm doing some things with these handlers: {:?}", self.handlers);
   }
}

What I'm trying to figure out is:

  • Is there any way to automagically register my the count_handler in the Service using only the manifest file?
    • Update: There seems to be no way to do this using only the manifest file, so let me clarify further
  • Is there any way to do this using some type of macro? (proc macro?)
  • I basically need whatever mapping is generated to happen without the user manually registering the handlers.

Ideally, I would be able to do the following:

fn main() {
  let manifest: Manifest = Manifest::from_file("config.yaml");
  // assert_eq!(manifest.event, "count".to_owned());
  // assert_eq!(manifest.handler, "count_handler".to_owned());

  let service = Service::new();
  service.start();
  // "I'm doing some things with these handlers: HashMap{ "count": Function(count_handler) }"
}

Some constrants and clarifications:

  • Assume the handler defined in the manifest, is always present in lib.rs
  • The count_handler could be any function that satisfies HandlerFn
  • The handler in the manifest, will always be indexed in the Service by the event
    • In this case, <"count", count_handler()> is registered in the Service

I don't think this is possible in Rust, but just wanted to get some clarity.

ra0x3
  • 41
  • 1
  • 4
  • 2
    There isn't a built-in way to look up functions by a string name. You'd need some mechanism to register functions by name with a `HashMap` that you can look up at runtime. – cdhowie Jun 29 '22 at 19:13
  • If you have control over how the handler functions are defined in `lib.rs`, you could have procedural macros generate a `String -> fn()` lookup table that you can use to map handler names to handlers. – EvilTak Jun 29 '22 at 20:08
  • @EvilTak Yea I'm thinking proc macros "might" help here. I'm not too versed in them, but I know they're powerful. Could you clarify what you mean by "if you have control over the handler functions are defined"? The handler functions will _always_ contrained by `fn()` (so I have control in that sense). But other than that, they can be named whatever. – ra0x3 Jun 29 '22 at 20:27
  • 1
    Can you access the manifest at compile time? If yes, a macro can do that. – Chayim Friedman Jun 29 '22 at 22:43
  • @ChayimFriedman I updated my question after @fresskoma's answer to reflect that yes, accessing the manifest at compile time is OK. I basically just don't want the user to _manually_ have to call the `.register()` ... the handlers should be magically registered. – ra0x3 Jun 30 '22 at 00:07
  • Since you have access to the manifest at compile time, you consider using a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) to generate Rust code representing the mapping from the contents of the manifest. Using a proc macro is [also possible](https://stackoverflow.com/q/58768109/4038191) if you prefer that approach instead. – EvilTak Jun 30 '22 at 16:05

1 Answers1

0

If you want to handle this at runtime, that is, not re-compile your application when you change your YAML file, then the answer is: no, it's no possible (without further effort). This is because Rust has no concept of runtime reflection, and as a result the name of your function is erased at compile time. There's simply no way to look it up.

As user EvilTak points out in the comments, you could build a supporting structure such that you end up with some sort of map from your handler function name (e.g. count_handler) to the reference to the function you can invoke at runtime, so e.g. HashMap<String, fn(Vec<u8>) -> Vec<u8>>.

However, this does not seem to meet your criteria of "using only the manifest file".


That being said, if you are okay with re-compiling your application every time you change the YAML file, then this is very much possible. The idea would then be to read your YAML file during compilation, in your macro, and generate the code that you would write yourself if you were manually wiring things up.

fresskoma
  • 25,481
  • 10
  • 85
  • 128
  • Thanks @fresskoma I had a hunch it was possible. I've updated my question to reflect: (1) If the manifest file is changed, the app should be recompiled anyway -- so "if you are okay with re-compiling your application every time you change the YAML file" satisfies my use case (2) I updated the question to clarify that I basically need this to happen without the user manually registering the handlers. Not sure if I have to open a new question, happy to do so. Or feel free to update your answer with an approach – ra0x3 Jun 30 '22 at 00:02
  • 1
    Ah, thanks for the clarification. I'll see if I can get around to update the answer, but it won't happen today :D Btw, well done with properly elaborating your first question. Very high quality compared to most other "first questions" people ask here on the platform! – fresskoma Jun 30 '22 at 14:38
  • No rush here. (And trust me I've been yelled at on SO enough times to know how to post a decent question lol) – ra0x3 Jun 30 '22 at 21:17