[SOLVED] Is there a better way to pass a callback function to a struct in Rust?


This Content is from Stack Overflow. Question asked by Geoffrey H

I’m building a game engine / CPU 3D renderer to learn the basics of rendering following a course.

I’m trying to organize the code so that things are encapsulated and I can provide a public API that’s easy to use.

For this, I want to provide this engine.on_update(|&mut engine| {}) method so users can pass in their own code and use the engine in there.
This callback is then executed as part of the engine’s update loop.

Here is what this looks like now (I trimmed the code to make this easier as its already quite long)

This is the engine module.

pub struct EngineConfig {
    pub window_title: String,
    pub width: usize,
    pub height: usize,
    pub clear_color: u32,

type EngineUpdateFn<'a> = &'a mut dyn FnMut(&mut Engine);

pub struct Engine<'a> {
    config: EngineConfig,
    canvas: Canvas<Window>,
    color_buffer: ColorBuffer,
    event_pump: EventPump,
    update: Option<EngineUpdateFn<'a>>,

impl<'a> Engine<'a> {
    pub fn build(mut config: EngineConfig) -> Engine<'a> {
        let ctx = sdl2::init().unwrap();
        let video = ctx.video().unwrap();

        match video.display_mode(0, 0) {
            Ok(mode) => {
                config.width = mode.w as usize;
                config.height = mode.h as usize;
                println!("Display mode: {:?}", mode);
            Err(e) => eprintln!(
                "Failed to get display mode: {}, using default width and height",

        let width = config.width;
        let height = config.height;

        let window = video
            .window(&config.window_title, width as u32, height as u32)

        let canvas = window.into_canvas().build().unwrap();

        let color_buffer = ColorBuffer::new(width, height);

        let event_pump = ctx.event_pump().unwrap();

        Engine {
            update: None,

    pub fn on_update(&mut self, update: EngineUpdateFn<'a>) {
        self.update = Some(update);

    fn custom_update(&mut self) {
        let f: *mut EngineUpdateFn<'a> = self.update.as_mut().unwrap();
        unsafe {

    fn update(&mut self) {
        let texture_creator = self.canvas.texture_creator();
        let mut texture = texture_creator
                self.config.width as u32,
                self.config.height as u32,

        let mut running = true;
        while running {
            running = self.process_input();

            self.render_buffer(&mut texture).unwrap();

            ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));

And in main.rs (Again trimmed of the mesh/points manipulations)

use renderer3d::prelude::*;
use vecx::Vec3;

pub fn main() {
    let mut eng = Engine::build(EngineConfig {
        window_title: "3d Renderer".to_string(),
        width: 800,
        height: 600,
        clear_color: 0xFF000000,

    let points = build_cube();

    let fov = 640.0;
    let cam_pos = Vec3(0.0, 0.0, -5.0);
    let mut rotation = Vec3(0.0, 0.0, 0.0);

    println!("Start update");
    eng.on_update(&mut |eng: &mut Engine| {
        eng.draw_grid(10, Some(0xFF333333));
        rotation = rotation + Vec3(0.01, 0.02, 0.0);

        let mut points = transform_points(&points, rotation);
        points = project_points(&points, cam_pos, fov);

        points.iter().for_each(|point| {
            let mut x = point.x();
            let mut y = point.y();

            x += eng.config().width as f64 / 2.0;
            y += eng.config().height as f64 / 2.0;

            eng.draw_rect(x as usize, y as usize, 4, 4, 0xFFFF0000);

As you can see, it’s important that this callback can access and mutate variables from the outer scope.

I’ve been through a lot of trial and error and I ended up finding something that works but the fact I have to do unsafe code makes me wonder if that’s really the correct way to do this.

Especially I’m worried that I’m trying to design this too much as I would in other less safe/restrictive languages and that I’m missing how to design this properly in Rust.


The problem is not the variables from the outer scope, it’s the fact that the callback has mutable access to Engine but it itself comes from engine, so there are two coexisting mutable references. Theoretically, it could for example access the captured variables both by the data pointer in Engine and by using them directly from different threads, causing a data race.

Since you probably don’t need access to the callback inside the callback, I would split Engine into two structs: one will contain the callback and the other, and have an update() and on_update() methods, and the other will include all other stuff. This way, the callback will take a mutable reference to the inner struct, which it can – since it is not contained within, but will still be able to call all engine’s functions.

This Question was asked in StackOverflow by Geoffrey H and Answered by Chayim Friedman It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?