Error on default implementation for trait's new method which return Self

Hi folks,

I want to add to my Feature trait new method implementation, since this method is similar for all existing features:

pub trait Feature: Send {
    fn new(
        sender: BroadcastSender<HandlerOutput>,
        receiver: BroadcastReceiver<HandlerOutput>
    ) -> Self where Self: Sized {
        Self {
            _receiver: receiver,
            _sender: sender,
        }
    }

    fn get_tasks(&mut self) -> Vec<JoinHandle<()>>;
}

but I got an error on compile:

error[E0071]: expected struct, variant or union type, found type parameter `Self`
  --> src/primary/traits/mod.rs:16:9
   |
16 |         Self {
   |         ^^^^ not a struct

Could somebody explain if there is a way to avoid this error without defining new method for each feature separately ?

You can't construct a Self with struct syntax in a trait, since that would restrict the possible implementers to only types which have those exact fields.

If every implementer of Feature will have those exact fields, maybe this could be a struct?

pub struct Feature<T> {
    receiver: BroadcastReceiver<HandlerOutput>,
    sender: BroadcastSender<HandlerOutput>,
    task_getter: T,
}

impl<T> Feature<T> {
    pub fn new(sender: BroadcastSender<HandlerOutput>, receiver: BroadcastReceiver<HandlerOutput>)
    where
        T: Default(),
    {
        Self {
            receiver: receiver,
            sender: sender,
            task_getter: T::default(),
        }
    }

    pub fn get_tasks(&mut self) -> Vec<JoinHandle<()>>
    where
        T: TaskGetter,
    {
        T::get_tasks(self)
    }
}

pub trait TaskGetter {
    fn get_tasks(feature: &mut Feature<Self>) -> Vec<JoinHandle<()>>;
}

You can use zero-sized types to make different kinds of Feature that don't have any extra fields.

1 Like

I am not sure if this should be a struct since each separate feature still need to impl own get_tasks method. And also there no guarantee that new method will be similar for all features.

This is the example, how I use it:

pub struct Console {
    _receiver: BroadcastReceiver<HandlerOutput>,
    _sender: BroadcastSender<HandlerOutput>,
}

impl Feature for Console {
    fn get_tasks(&mut self) -> Vec<JoinHandle<()>> {
        let mut receiver = self._receiver.clone();

        let handle_input = || {
            tokio::spawn(async move {
                loop {
                    if let Ok(output) = receiver.recv().await {
                        match output {
                            HandlerOutput::SuccessMessage(message, _) => {
                                let text = format!("[SUCCESS]: {}", message);
                                println!("{}", text.bright_green());
                            },
                            // ...
                            _ => {},
                        }
                    }
                }
            })
        };

        vec![
            handle_input(),
        ]
    }
}

and in the main code:

let mut features: Vec<Box<dyn Feature>> = options.external_features;
cfg_if! {
    if #[cfg(feature = "ui")] {
        use crate::features::ui::UI;

        let ui = UI::new(query_sender.clone(), query_receiver.clone());
        features.push(Box::new(ui));
    } else if #[cfg(feature = "console")] {
        use crate::features::console::Console;

        let console = Console::new(query_sender.clone(), query_receiver.clone());
        features.push(Box::new(console));
    }
}

I do not see the way how I would implement smth like that in case Feature can be a struct

I don't see how you could avoid implementing new for each implementor of Feature. But I also don't see why that would be a problem.

First of all, I just want to avoid code duplication where possible.

Second, probably rare use-case: in case if developer who include my library will want to implement own features, he will need explicitly include async_broadcast dependency to own Cargo.toml. I believe, the next code would work even without this step (including async_broadcast to 3rd-party app deps):

impl Feature for Console {
    fn get_tasks(&mut self) -> Vec<JoinHandle<()>> {
        let mut receiver = self._receiver.clone();

        let handle_input = || {
            tokio::spawn(async move {
                loop {
                    if let Ok(output) = receiver.recv().await {
                        match output {
                            HandlerOutput::SuccessMessage(message, _) => {
                                let text = format!("[SUCCESS]: {}", message);
                                println!("{}", text.bright_green());
                            },
                            // ...
                            _ => {},
                        }
                    }
                }
            })
        };

        vec![
            handle_input(),
        ]
    }
}

So only if developer will want to use external channel (which is supported by my lib), he will want to include async_broadcast explicitly.

You could re-export async_broadcast::{BroadcastReceiver, BroadcastSender}.

Do you mean, it is possible to re-export from my lib crate ? If I use just this:

use async_broadcast::{Sender as BroadcastSender, Receiver as BroadcastReceiver};

I will have an error:

error[E0432]: unresolved import `async_broadcast`
 --> src/features/ai/mod.rs:1:5
  |
1 | use async_broadcast::{Sender as BroadcastSender, Receiver as BroadcastReceiver};
  |     ^^^^^^^^^^^^^^^ use of undeclared crate or module `async_broadcast`

Is there a way to avoid adding this dependency explicitly ?

Yes, with pub use async_broadcast::{Sender as BroadcastSender, Receiver as BroadcastReceiver};

Hmm, I'm not sure I fully understand your setting. You must import async_broadcast somewhere where you use the BroadcastSender and BroadcastReceiver types. Why don't you re-export from there?

I was not sure if exporting third-party dependencies is good way. It's like exporting tokio and so on. At least, I do not know any other crate which do the same.

I'd say it is not uncommon for libraries to re-export third party types. Re-exporting something as ubiquitous as tokio might not be something you see often in the wild, but certainly smaller crates that offer types that are used in interfaces. Pretty much every web framework I used re-exports http, mime, bytes, etc.

1 Like

Got it. Thank you for the good hint.

1 Like