Rust Callbacks, DWIW

Sun, 24 Jul 2022

Rust Callbacks, DWIW

This is a short practical post on how to store callbacks in a struct with full generality, allowing the user of your library maximal ease and flexibility. I’ll leave explanations and details to other sources.

The following code stores callbacks of functions Fn(usize) -> usize, but any function signature or return value would work fine.

Code is available at https://github.com/duelafn/blog-code/tree/main/2022/rust-callbacks-dwiw

pub struct MyStruct<'a> {
    callbacks: Vec<Box<dyn Fn(usize) -> usize + 'a>>,
}

impl<'a> MyStruct<'a> {
    pub fn new() -> MyStruct<'a> {
        MyStruct { callbacks: Vec::new() }
    }

    pub fn add_callback(&mut self, func: impl Fn(usize) -> usize + 'a) {
        self.callbacks.push(Box::new(func));
    }

    pub fn trigger(&self, val: usize) {
        for (i, cb) in self.callbacks.iter().enumerate() {
            println!("{}: {}", i, cb(val));
        }
    }
}


fn cb1(val: usize) -> usize { val + 1 }

fn main() {
    let borrowed_content = String::from("Foo");

    // "borrowed_content" must outlive "obj". It is sufficient to declare
    // "obj" second, but I've created an explicit scope here for clarity.
    {
        let mut obj = MyStruct::new();
        let moved_content = String::from("Foo");
        // Functions (reusable)
        obj.add_callback(cb1);
        obj.add_callback(cb1);
        // Lambdas
        obj.add_callback(|n| n + 3);
        // Closure owning its variables
        obj.add_callback(move |n| n + moved_content.len());
        // Closure borrowing non-static variables
        obj.add_callback(|n| n + borrowed_content.len());

        obj.trigger(6);
    }
}

So far as I know, there is no way to replace the Fn(usize) -> usize with any type alias or supertrait – I believe one has to spell it out explicitly each use.

Set Zero

Home

2023

2022

2021

2020