1

In one of my projects, I would like to store a function pointer used as a callback to change the state of a struct. I've tried different things, but always encountered errors.

Consider the following situation (playground):

struct CallbackStruct {
    callback: fn(i32) -> i32,
}

struct MainStruct {
    pub callback_struct: Vec<CallbackStruct>,
    pub intern_state: i32,
}

impl MainStruct {
    pub fn new() -> MainStruct {
        let result = MainStruct {
            callback_struct: Vec::new(),
            intern_state: 0,
        };
        // push a new call back struct
        result.callback_struct.push(CallbackStruct{callback: result.do_stuff});
        
        return result;
    }

    pub fn do_stuff(&mut self, i: i32) -> i32 {
        self.intern_state = i * 2 + 1;
        self.intern_state
    }
}

fn main() {
    let my_struct = MainStruct::new();
}

Here, I'm trying to keep a callback to the MainStruct, that can change it's internal state. This callback will only be stored by other structs owned by this main structure, so I don't think I'm having lifetimes issues - as long as the callback exists, the main struct does as well as it kind of own it.

Now, I'm wondering if having such a callback (with the &mut self reference) isn't a borrow of the main struct, preventing me from having multiple of them, or even keeping them?

In the example, I'm keeping a Vec of the CallbackStruct because I may have different structs all having these kinds of callbacks.

In c/c++, I can go with functions pointers, and I couldn't find a way to store such things in Rust.

How would one implement such a thing?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Does this answer your question? [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Finomnis Sep 14 '22 at 14:00
  • I think you are trying to apply a programming pattern from C++ that isn't compatible with Rust. The programming pattern does not have a clear notion of ownership, which is something that Rust requires, but C++ doesn't. Callback functions in Rust are mainly for passing executable arguments, they are not meant to be stored and kept around. Otherwise you will get major problems with ownership. – Finomnis Sep 14 '22 at 14:01
  • Note that the big difference between C++ objects and Rust objects is that Rust objects are implicitly movable. Therefore you can't store a self-reference (like a callback) in them, because they might get moved at any point. (of course unless a reference exists, but that doesn't work with self-references, because that would cause a weird paradoxical situation for the borrow checker) – Finomnis Sep 14 '22 at 14:05

1 Answers1

2

The problem that Rust is preventing is the possibility that being able to mutate the struct that holds the callback allows you to mutate or destroy the callback while it is executing. That is a problem.

If it is a common pattern that you want your callbacks to modify the structure that invokes the callback, then there's a couple options. Both require adjusting the function signature to pass-in data they are allowed to mutate. This would also allow multiple callbacks to mutate the same state since they only hold the mutable reference while they are running.

#1: Keep the state and callbacks separate

The idea is that the callback is explicitly given mutable access to the state which does not include the callback itself. This can be done by constructing a separate structure to hold the non-callback data, as shown here, or you can simply pass in multiple parameters:

struct InternalState(i32);

impl InternalState {
    fn do_stuff(&mut self, i: i32) {
        self.0 = i * 2 + 1;
    }
}

struct MainStruct {
    callbacks: Vec<Box<dyn FnMut(&mut InternalState)>>,
    state: InternalState,
}

impl MainStruct {
    fn new() -> MainStruct {
        MainStruct {
            callbacks: Vec::new(),
            state: InternalState(0),
        }
    }

    fn push(&mut self, f: Box<dyn FnMut(&mut InternalState)>) {
        self.callbacks.push(f);
    }
    
    fn invoke(&mut self) {
        for callback in &mut self.callbacks {
            callback(&mut self.state)
        }
    }
}

fn main() {
    let mut my_struct = MainStruct::new();
    
    my_struct.push(Box::new(|state| { state.do_stuff(1); }));
    my_struct.push(Box::new(|state| { state.do_stuff(2); }));
    
    my_struct.invoke();
    
    dbg!(my_struct.state.0);
}

#2 Remove the callback from the struct before executing it

You can mutate the whole struct itself if you remove the callback being ran. This can be done via the take-and-replace method used in this question. This has the added benefit that you have the opportunity to add new callbacks; you just have to reconcile the two sets of callbacks when putting them back:

struct InternalState(i32);

impl InternalState {
    fn do_stuff(&mut self, i: i32) {
        self.0 = i * 2 + 1;
    }
}

struct MainStruct {
    callbacks: Vec<Box<dyn FnMut(&mut MainStruct)>>,
    state: InternalState,
}

impl MainStruct {
    fn new() -> MainStruct {
        MainStruct {
            callbacks: Vec::new(),
            state: InternalState(0),
        }
    }

    fn push(&mut self, f: Box<dyn FnMut(&mut MainStruct)>) {
        self.callbacks.push(f);
    }
    
    fn invoke(&mut self) {
        let mut callbacks = std::mem::take(&mut self.callbacks);
        for callback in &mut callbacks {
            callback(self)
        }
        self.callbacks = callbacks;
    }
}

fn main() {
    let mut my_struct = MainStruct::new();
    
    my_struct.push(Box::new(|main| { main.state.do_stuff(1); }));
    my_struct.push(Box::new(|main| { main.state.do_stuff(2); }));
    
    my_struct.invoke();
    
    dbg!(my_struct.state.0);
}

You'll also noticed I changed the code from function pointers fn(i32) -> i32 to function trait objects Box<dyn FnMut(i32) -> i32> since the latter is much more flexible and common since it can actually capture other variables if needed.

kmdreko
  • 42,554
  • 6
  • 57
  • 106