Generic struct with optional default value for a field

Hi,

I have some code where I'd like to have a generic struct. The struct has a field of the generic type, which can be initialized in the new function either from an argument or from a default value.

pub struct Node<T, C: HandleMessage + SendMessage> {
    pub channel: C,
}

impl<T, C> Node<T, C>
where
    C: HandleMessage + SendMessage,
{
    pub fn new(props: T, channel: Option<C>) -> Self {
        Node {
            channel: if let Some(channel) = channel {
                channel
            } else {
                Channel::new()
            },
        }
    }
}

pub struct Channel;

trait SendMessage {
    fn send(&self, data: String);
}

trait HandleMessage {
    fn on_data(&self, handler: impl FnMut(String));
}

impl Channel {
    fn new() -> Self {
        Channel {}
    }
}

impl HandleMessage for Channel {
    fn on_data(&self, mut handler: impl FnMut(String)) {
        // code removed for brevity
    }
}

impl SendMessage for Channel {
    fn send(&self, data: String) {
        // code removed for brevity
    }
}

The error I get is

error[E0308]: mismatched types
   --> src/node.rs:166:17
    |
157 | impl<T, C> Node<T, C>
    |         - this type parameter
...
166 |                 Channel::new()
    |                 ^^^^^^^^^^^^^^ expected type parameter `C`, found `Channel`
    |
    = note: expected type parameter `C`
                       found struct `Channe

From what I have read, the first problem is that the compiler expects to create a struct with a channel field with the size of C. But if I return a Channel that size can be different. I have tried using a Box & dyn but had no luck.

So the question is, how can I have a struct with a field that can be initialized either from a generic optional argument or created from a harcoded type?

This doesn't make sense (that's why you get a type error). If your option is None, you are trying to return a Node<T, Channel> even though your return type is Self, ie. Node<T, C> for whatever C was chosen to be by the caller. Since C can be anything, not just the concrete Channel type, this can't possibly work with static typing like that.

You can instead require that C: Default and use Option::unwrap_or_default to supply its default value when the optional is None.

Or you can simply provide two constructors, one unary that exists on, and returns, Node<T, C>; and another nullary one that only exists on (and returns) Node<T, Channel>.

You can't have runtime decisions change types. You'd typically do this with two different methods:

impl<T> Node<T, Channel> {
    pub fn new(props: T) -> Self {
        Node { channel: Channel::new() }
    }
}

impl<T, C> Node<T, C>
where C: HandleMessage + SendMessage {
    pub fn with_channel(props: T, channel: C) -> Self {
        Node { channel }
    }
}

(Of course, this won't work because the T type parameter doesn't do anything, but I just woke up and it's too early... I'm sure you'll find out why you can't do that in due time...)

1 Like