audunhalland / entrait

Loosely coupled Rust application design made easy
83 stars 1 forks source link

Traits with multiple methods #17

Closed canac closed 1 year ago

canac commented 1 year ago

I'm really intrigued by this implementation of dependency injection in Rust! One issue I was running into was how to create a trait with multiple methods.

I tried this

#[entrait]
pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String> {
        todo!()
    }

    fn read_var_maybe(&self, var: &str) -> Option<String> {
        todo!()
    }
}

but then I get an error about entrait::Impl not implementing Environment, when I try to use the app in my code.

This is the relevant generated code:

pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String>;
    fn read_var_maybe(&self, var: &str) -> Option<String>;
}
impl<EntraitT: Sync> Environment for ::entrait::Impl<EntraitT>
where
    EntraitT: Environment + Sync,
{
    #[inline]
    fn read_var(&self, var: &str) -> Result<String> {
        self.as_ref().read_var(var)
    }
    #[inline]
    fn read_var_maybe(&self, var: &str) -> Option<String> {
        self.as_ref().read_var_maybe(var)
    }
}

I can get it working if I remove the entrait macro and modify it to this:

pub trait Environment {
    fn read_var(&self, var: &str) -> Result<String>;
    fn read_var_maybe(&self, var: &str) -> Option<String>;
}

fn read_var(_deps: &impl std::any::Any, var: &str) -> Result<String> {
    unimplemented!()
}

fn read_var_maybe(_deps: &impl std::any::Any, var: &str) -> Option<String> {
    unimplemented!()
}

impl<EntraitT: Sync> Environment for ::entrait::Impl<EntraitT>
where
    EntraitT: Sync,
{
    #[inline]
    fn read_var(&self, var: &str) -> Result<String> {
        read_var(self, var)
    }
    #[inline]
    fn read_var_maybe(&self, var: &str) -> Option<String> {
        read_var_maybe(self, var)
    }
}

I have to remove the Environment trait bound and change how read_var and read_var_maybe are called. I could definitely be doing something wrong, but it seems like the generated output doesn't compile for traits with multiple methods. Do you have any insights here? If it doesn't work yet, is this a use-case you'd be willing to support?

audunhalland commented 1 year ago

I think maybe you're trying to do this: https://docs.rs/entrait/0.4.6/entrait/index.html#case-2-hand-written-trait-as-a-leaf-dependency (Hand-written trait as a leaf dependency), correct me if I'm wrong.

If you're truly making a leaf dependency (i.e. needs no further entrait abstractions from within implementations), the way to fix the code is to leave the trait methods unimplemented and do impl Environment for YourApp { ... } instead, and specify the implementations there. That way you can also use data/state stored in your app.

canac commented 1 year ago

That approach works. I also realized that I can also do:

#[entrait(pub Environment)]
mod environment {
    pub fn read_var(_deps: &impl std::any::Any, var: &str) -> Result<String> {
        todo!()
    }

    pub fn read_var_maybe(_deps: &impl std::any::Any, var: &str) -> Option<String> {
        todo!()
    }
}

as described in the Module support section of the Readme to get a single trait with multiple methods. Sorry I missed that!