How to pass a closure as an extern C function

Now that we don’t have to worry about debugging it, let me try to explain what’s going on:

  • RefCell is a single-threaded lock; it only allows one mutable reference to exist at a time. The lock is released automatically when the return value from borrow_mut() is dropped— this is known as the RAII pattern, and is used extensively in Rust.
  • The BorrowMutErrors mean that something tried to take the lock while it was being held elsewhere. The line App::get().run(); is essentially shorthand for this:
let tmp = App::get();  // Acquire the lock
tmp.run();
std::mem::drop(tmp);  // Destroy the temporary, releasing the lock
  • Because the lock was held here, any attempt to call App::get() inside run failed, even if it was deeply nested in a callback.

Now, let me annotate the new run() code:

pub fn run() {
    let app = Self::get();  // Acquire the lock
    app.dialog.build();
    if !IUP.show_xy(app.dialog.dialog, iup::MOUSEPOS, iup::MOUSEPOS) {
        std::mem::drop(app);  // Release the lock
        IUP.main_loop();      // Callbacks happen here
        Self::get().save();   // Reacquire the lock to save
                              // Release lock at end of statement.
    } else {
        println!("Failed to show main window");
    }
}
1 Like

Thanks, that helped:-)
Incidentally, the save line is now Self::get().dialog.save(); since I moved save() into Dialog. The reason being is that I can't call App methods from the dialog's methods. Anyway, it all works so now I'll try to move on to the next step.

Once again I hit a wall with thread 'main' panicked at 'already borrowed: BorrowMutError'. This time I'd added a timer to Dialog:

struct Dialog {
   ...
    fn make_timer(&mut self) {
        self.timer = IUP.timer();
        IUP.set_attribute(self.timer, iup::RUN, iup::NO);
        IUP.set_int(self.timer, iup::TIME, 100); // ms 
        IUP.set_callback(self.timer, iup::ACTION_CB, on_timer);
    }
    fn on_timer(&mut self) -> i32 {
        println!("tick");
        iup::DEFAULT
    }
   ...
}
extern "C" fn on_timer(_ih: *mut Ihandle) -> i32 {
    App::get().dialog.on_timer()
}

If I comment out the set_callback the GUI comes up and works (except for the timer of course); but if I leave it as-is I get the crash.

So I've reverted to using

static mut _APP: *mut App = ptr::null_mut();

fn app() -> &'static mut App { unsafe { &mut *_APP } }

fn main() {
    let mut _app = App::new();
    unsafe { _APP = &mut _app; }
    app().run();
}

With this in place and my callbacks like this:

extern "C" fn on_timer(_ih: *mut Ihandle) -> i32 { app().dialog.on_timer() }

And now everything works.

That’s moderately concerning, as it probably means the GUI is calling a callback function in the middle of processing another, which could change state you’re not expecting it to. There’s not much you can do from this end to fix that, though, other than a major re-architecture that’s probably not worth the effort.

You’d probably need to set up your own rust-side event queue, have the callbacks push a message there, and then have an idle handler in the main loop to process them. If you see hard-to-explain bugs in the future, it might be worth considering, but there’s also a decent chance your current silution can be made to work if you’re careful; you just won’t be able to lean as heavily on Rust’s crrectness guarantees.

1 Like

Sadly, no. &mut in Rust expresses exclusivity, which is the opposite of concurrency.

Indeed, parallelism (multiple threads) implies concurrency, but you can have concurrency without parallelism, usually with reentrancy (some code calling a callback the obtains a concurrent &mut) or interrupts (some executor or OS interrupting the "main normal thread" to execute some special "callback" code; at that point, despite the lack of true parallelism, there is little difference with single-core multi-threading).

And indeed, those BorrowMut errors are Rust detecting a re-entrancy problem and saving you from UB. So, by switching to static muts with raw pointers, you are just silencing the Rust red flags without truly fixing them :grimacing:


Now that we better know what your code is doing, I suggest the following course of action: stop using &muts altogether, the semantics of such references did not exist at the time these low-level raw APIs were created. C, and other programs, only have shared pointers / references, i.e., & _ references.

  • and whenever you have a field you wish to mutate, wrap it in a Cell (or in a RefCell, but then make sure to have borrows that are very short-lived).

If you only use & shared references, and Cells to perform the necessary mutations, your program will have no UB and will not panic!

pub
struct App {
    pub unsaved_changes: bool,
    pub dialog: Dialog,
}

impl App {
    fn new ()
      -> Self
    {
        App {
            dialog: Dialog::new(),
            unsaved_changes: false,
        }
    }

    fn get ()
      -> &'static App
    {
        thread_local! {
            static INSTANCE: &'static App = Box::leak(Box::new(App::new()));
        }
        INSTANCE.with(|&it| it)
    }

    pub
    fn run (self: &'_ Self) // no &mut
    {
        self.dialog.build();
        if IUP.show_xy(self.dialog.dialog, iup::MOUSEPOS, iup::MOUSEPOS) {
            IUP.main_loop();
            self.save();
        } else {
            println!("Failed to show main window");
        }
    }
}

// dialog.rs
extern "C"
fn on_ok (_ih: *mut Ihandle)
  -> i32
{
    App::get()
        .dialog
        .on_ok() // must take `&self`
}

use ::core::cell::Cell;

struct Dialog {
    timer: Cell<Timer...>, // Inner mutation, use `.timer.set(...)` instead of `.timer = ...`
    ...
}

impl Dialog {
    fn make_timer (self: &'_ Self) // no &mut
    {
        let timer = IUP.timer();
        self.timer.set(timer); // instead of self.timer = timer;
        IUP.set_attribute(timer, iup::RUN, iup::NO);
        IUP.set_int(timer, iup::TIME, 100); // ms 
        IUP.set_callback(timer, iup::ACTION_CB, on_timer);
    }

    fn on_timer (self: &'_ Self)
      -> i32
    {
        println!("tick");
        iup::DEFAULT
    }
}

// main.rs
fn main ()
{
    App::get().run();
    IUP.close();
}
1 Like

Thank you and the many people who've helped.

I've now learnt more about the IUP library. It works differerently from any GUI library I've ever used before being purely procedural. And it turns out I don't need a global App object at all. Even though callbacks only get an Ihandle (a C pointer), IUP has a function for getting an Ihandle to the containing dialog within the callback and a way of getting access to the widgets.

Here is the link to my experimental and very incomplete IUP GUI bindings and to an example which shows them in use.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.