Not working as expected the return of the build function

I'm trying to implement a strategy design pattern in rust, but my code is not working.

I would like to build the strategy and regardless of which strategy i've created, I would like to call it from its abstraction, not its implementation.

The error is:

mismatched types
expected type parameter T
found struct SmsNotificationStrategy

inside the function build in the notificator struct.

use std::result;
use std::error;
use std::fmt;
use std::convert;

#[derive(Debug)]
struct NotificationError;

impl fmt::Display for NotificationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error while sending notification")
    }
}

impl error::Error for NotificationError {}

trait NotificationStrategy {
    fn notify() -> result::Result<(), NotificationError>;
}

struct SmsNotificationStrategy;

impl NotificationStrategy for SmsNotificationStrategy {
    fn notify() -> result::Result<(), NotificationError> {
        println!("Sending Sms Notification");
        Ok(())
    }
}

struct EmailNotificationStrategy;

impl NotificationStrategy for EmailNotificationStrategy {
    fn notify() -> result::Result<(), NotificationError> {
        println!("Sending an Email notification");
        Ok(())
    }
}

struct NotImplementedNotificationStrategy;

impl NotificationStrategy for NotImplementedNotificationStrategy {
    fn notify() -> result::Result<(), NotificationError> {
        Err(NotificationError)
    }
}

enum NotificationType {
    Sms,
    Email,
    NotImplemented,
}

impl convert::From<&str> for NotificationType {
    fn from(value: &str) -> Self {
        match value.to_uppercase().as_str() {
            "SMS" => NotificationType::Sms,
            "EMAIL" => NotificationType::Email,
            _ => NotificationType::NotImplemented,
        }
    }
}

struct Notificator<T: NotificationStrategy> {
    notification_strategy: Box<T>,
}

impl <T:NotificationStrategy> Notificator <T> {
    // the error happens in this function.
    fn build(notification_type: NotificationType) -> Self {
        match notification_type {
            NotificationType::Sms => Self {
                notification_strategy: Box::new(SmsNotificationStrategy),
            },
            NotificationType::Email => Self {
                notification_strategy: Box::new(EmailNotificationStrategy),
            },
            NotificationType::NotImplemented => Self {
                notification_strategy: Box::new(NotImplementedNotificationStrategy),
            },
        }
    }

    fn notify(&self) -> result::Result<(), NotificationError> {
        self.notification_strategy::notify()
    }
}

fn main() {
    let notification_type_from_str = "SMS";
    let notification_type = NotificationType::from(notification_type_from_str);

    let notification_strategy: Notificator<SmsNotificationStrategy> = Notificator::build(notification_type);

    let notification_result = notification_strategy.notify();

    match notification_result {
        Ok(()) => println!("Notification was sent successfully"),
        Err(_) => eprintln!("An error occurred while sending notification"),
    }
}

Say I were to implement NotificationStrategy for MyNotificationStrategy and then I would call Notificator::<MyNotificationStrategy>::build(NotificationType::Sms). I wouldn't get a a Notificator<MyNotificationStrategy>, but a Notificator<SmsNotificationStrategy>. That violates the rule that the generic parameter is chosen by the caller (me), not the callee (your build method). You should get rid of T on Notificator and store the notification strategy either as a trait object or an enum (or a combination of enum and trait object where you have one variant of the enum storing a dyn NotificationStrategy trait object). See this thread for more information. I personally would simplify your example into this (which would be the enum-based version I listed above).

1 Like