dtolnay / inventory

Typed distributed plugin registration
Apache License 2.0
948 stars 43 forks source link

[Help] Collect functions #55

Closed TornaxO7 closed 1 year ago

TornaxO7 commented 1 year ago

Hi! I'd happy if you have some tips for me, because I'm a bit stuck at the moment.

Context

I have different commands whith have some common functions, one of them is a function to "parse" a string (in this case) and return themself if they match. I'm trying to collect those functions with your crate to be able to iterate through the functions

Code

Now here comes the relevant code:

inventory::collect!(dyn Fn(NormalizedCommand) -> Option<Box<dyn Command>>);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NormalizedCommand(String);

pub trait Command {
    fn normalize(&self) -> NormalizedCommand;

    // the inventory should collect this function from all structs which implement the `Command` trait.
    fn from_normalized(generalized: NormalizedCommand) -> Option<Box<Self>>
    where
        Self: Sized;

    fn execute(&self, worker: &mut Worker);
}

pub fn unnormalize<'a>(general: NormalizedCommand) -> Result<Box<dyn Command>, CommandError> {
    for command in inventory::iter::<Box<dyn Command>> {
        if let Some(matching_command) = command.from_general(general) {
            return Ok(matching_command);
        }
    }

    Err(CommandError::UnmatchingCommand)
}

Output

I'm getting the following error message:

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> lefthk-core/src/config/command/mod.rs:16:1
   |
16 | inventory::collect!(dyn Fn(NormalizedCommand) -> Option<Box<dyn Command>>);
   | ^^^^^^^^^^^^^^^^^^^^-----------------------------------------------------^
   | |                   |
   | |                   `dyn std::ops::Fn(config::command::NormalizedCommand) -> std::option::Opt
ion<std::boxed::Box<dyn config::command::Command>>` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead
   = note: this error originates in the macro `inventory::collect` (in Nightly builds, run with -Z
 macro-backtrace for more info)

Question

Do you have an idea how I can fix this?

dtolnay commented 1 year ago

"impl doesn't use only types from inside the current crate" — the type in collect! needs to be defined in your crate. std::ops::Fn is not defined in your crate.

You could do it like this:

pub struct CommandFromNormalized(fn(NormalizedCommand) -> Option<Box<dyn Command>>);

impl CommandFromNormalized {
    const fn new<T: Command + 'static>() -> Self {
        CommandFromNormalized(|general: NormalizedCommand| {
            T::from_normalized(general).map(|cmd| cmd as Box<dyn Command>)
        })
    }
}

inventory::collect!(CommandFromNormalized);

…

inventory::submit!(CommandFromNormalized::new::<MyCommand>());

…

    for command in inventory::iter::<CommandFromNormalized> {
        if let Some(matching_command) = (command.0)(general) {
TornaxO7 commented 1 year ago

yay, it's working (the compiler doesn't complain anymore)! Thank you! happy pikadsu noises