zar3bski
zar3bski

Reputation: 3181

function pointer to instance method

Context

Task

I'm trying trigger notifications based on the changes of TaskStatus (enum). A Task is the cornerstone of my program and is linked to a NotifierService. I set things so that set_status calls the notifier

pub type NotifierService = fn(&Task);

pub struct Task<'a> {
    pub status: TaskStatus,
    pub message_id: String,
    pub magnet_link: String,
    pub notifier: &'a NotifierService,
}

impl<'a> Task<'a> {
    pub fn new(magnet_link: String, message_id: String, notifier: &'a NotifierService) -> Self {
        Self {
            magnet_link,
            message_id,
            status: TaskStatus::RECEIVED,
            notifier,
        }
    }

    pub fn set_status(mut self, status: TaskStatus) {
        self.status = status;
        (self.notifier)(&self);
    }
}

Create services using NotifierService

So far so good. What I would like to do is to override -- probably not the good term -- NotifierService so that it points to a method of my choosing every time I use it in composition somewhere. For example

pub struct DiscordService {
    client: Client,
    headers: HeaderMap,
    notifier: NotifierService,
}

impl MessagingService for DiscordService {
    fn new() -> Self {
        let client = Client::new();
        let mut headers = HeaderMap::new();
        let notifier = Self::update_task_status; //self does not exist yet, how to point to this method

        return Self {
            client,
            headers,
            notifier,
        };
    }
    fn update_task_status(&self, task: &Task) {.....

Problem

I obviously experience a

mismatched types
expected fn pointer `for<'a, 'b> fn(&'a Task<'b>)`
      found fn item `for<'a, 'b, 'c> fn(&'a DiscordService, &'b Task<'c>)

Since self is not defined at this point in the constructor, I can't simply say

let notifier = |task: &Task|Self::update_task_status(self:????, task)

Is there a way so I could provide a method as NotifierService every time I use it in composition in new impl? Could the definition of NotifierService contain a dynamic reference -- again, probably not the right term -- to accept different method(&self, task: &Task)

pub type NotifierService = fn(??<dyn>??, &Task);

I am fairly new to rust and I struggle with this notion of types being function pointers (among other things). I would like Task and the notification to be as service agnostic as possible but I am not sure this is the rustacean way.

Upvotes: 0

Views: 61

Answers (1)

zar3bski
zar3bski

Reputation: 3181

Thanks @kmdreko, the dyn field was the answer. Here is the code. No need to add anything to the DiscordService but it's new implementation needed some tuning.

pub struct Task<'a> {
    pub status: TaskStatus,
    pub message_id: String,
    pub magnet_link: String,
    pub notifier: &'a dyn MessagingService,
}

impl<'a> Task<'a> {
    pub fn new(
        magnet_link: String,
        message_id: String,
        notifier: &'a dyn MessagingService,
    ) -> Self {
        Self {
            magnet_link,
            message_id,
            status: TaskStatus::RECEIVED,
            notifier,
        }
    }
    // Update private field status and call the associated
    // notifier
    pub fn set_status(mut self, status: TaskStatus) {
        self.status = status;
        self.notifier.update_task_status(&self);
    }

    pub fn get_status(&self) -> String {
        self.status.to_string()
    }
}

As you pointed out, this works because of a Sized restriction on the trait.

pub trait MessagingService {
    fn new() -> Self
    where
        Self: Sized;
    fn fetch_tasks(&self) -> Option<Vec<Task>>;
    fn update_task_status(&self, task: &Task);
}

This is the kind of tricks I missed to transpose my OOP to rust. Thanks

Upvotes: 1

Related Questions