How to pass a closure as an extern C function

You can't use a Rust closure for your Icallback. The Icallback type is just a function pointer and can't contain any state (where would you put it? that pointer points directly into your a read-only copy of your executable's code).

Your options are to either use static variables to pass information between the callback and the caller (not recommended because of data races and thread-safety), or to go upstream and change the callback (and any functions it is passed to) to accept an extra void * pointer that can be used for your closure's state.

This article might help you understand what's going on and how you can approach the problem in Rust:

2 Likes

Thank both of you for your replies.
Unfortunately, I cannot change the C API since it is outside my control.
Since the IUP library is a GUI library it must always be used in the main thread, so it will never be passed between threads.
To this end I tried to create a global static "application" object that I could then access in the extern C functions I pass as callbacks. I've tried various approaches, none of which has compiled.

Here's my first failed attempt:

static mut APP: Option<*mut App> = None;

fn main() {
    unsafe { APP = Some(&mut App::new()); }
    app().run();
    IUP.close();
}

fn app() -> *mut App {
    APP.unwrap()
}
...
struct App { ... }
impl App {
    fn new() -> App { ... }
    fn run(&mut self) { self.run_xy_save(0, 0, false); }
}

This fails with:

error[E0599]: no method named `run` found for raw pointer `*mut App` in the current scope
  --> src/main.rs:11:11
   |
11 |     app().run();
   |           ^^^ method not found in `*mut App`

This is a pity because it most closely expresses what I want to do. It would mean that my extern C callbacks could call app().method() which is what I want to achieve.

I next tried with the once_cell crate which I've only just discovered:

use once_cell::unsync::OnceCell; // I don't need thread-safe access
static APP: OnceCell<App> = OnceCell::new();

fn main() {
    APP.set(App::new()).unwrap();
    APP.get_mut().unwrap().run();
    IUP.close();
}

This fails with:

error[E0277]: `std::cell::UnsafeCell<std::option::Option<App>>` cannot be shared between threads safely
  --> src/main.rs:21:1
   |
21 | static APP: OnceCell<App> = OnceCell::new();
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::cell::UnsafeCell<std::option::Option<App>>` cannot be shared between threads safely
   |
   = help: within `once_cell::unsync::OnceCell<App>`, the trait `std::marker::Sync` is not implemented for `std::cell::UnsafeCell<std::option::Option<App>>`
   = note: required because it appears within the type `once_cell::unsync::OnceCell<App>`
   = note: shared static variables must have a type that implements `Sync`

error[E0596]: cannot borrow immutable static item `APP` as mutable
  --> src/main.rs:26:5
   |
26 |     APP.get_mut().unwrap().run();
   |     ^^^ cannot borrow as mutable

error: aborting due to 2 previous errors

I really hope there'a a way to implement the global app() function so that it returns a mutable App pointer or reference that I can then call mutating methods on.

But I can't figure out how to do it:-(

For your first attempt, you will need to use the unsafe function as_mut to obtain a reference before calling methods:

unsafe { app().as_mut().unwrap().run(); }

or directly dereference the pointer if you are 100% sure that it is non-null:

unsafe { (*app()).run(); }

I suppose you can also make app return a &mut App directly in this way.

In fact, I would say that the type of APP can be just *mut App, since we have null pointers:

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

Now we don't need unwrap.

Also, this doesn't look right:

unsafe { APP = Some(&mut App::new()); }

as the temporary will be dropped at the end of the expression. You need to bind it to a variable so that its lifetime extends until the end of main: (sorry for the poor naming)

let mut the_app = App::new();
unsafe { APP = Some(&mut the_app); }

I'm pretty sure using a dedicated crate is a good idea, but that's unfortunately out of my scope :neutral_face:

I haven't used it myself but during my investigations of Rust-C interoperability I came across libffi - Rust, which might give you greater flexibility how you pass lambdas as C callbacks.

3 Likes

Thank you!

Here's an outline of how I got it to work based on your advice.

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

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

fn main() {
    let mut _app = App::new(); // These 2 lines *must* be first
    unsafe { _APP = &mut _app; }
    app().run();
    IUP.close();
}

Here's an extract from the App impl:

    fn make_bindings(&mut self) {
        IUP.set_callback(self.main_version_button, "ACTION", main_on_version);
        ...
    }

    fn on_version(&mut self) -> i32 {
        IUP.version_show();
        iup::DEFAULT // IUP callbacks must return DEFAULT or CLOSE (or some others)
    }

And here's the global C function used as a callback that binds it all together:

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

And it all works, so now I can continue and see how far I can go with it.

PS I tried this

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

fn app() -> &'static mut App {
    unsafe {
        if _APP.is_null() {
            let mut _app = App::new();
            unsafe { _APP = &mut _app; }
        }
        &mut *_APP
    }
}

fn main() {
    app().run();
    IUP.close();
}

But it core dumped. My guess is that _app doesn't live long enough. Anyway, I don't care, I only have to do the same two lines at the start of main() to get the GUI going.

Don't use static mut, they are very dangerous. Here is a post I wrote that describes how to safely use global data to hack your way into making a function carry state: How to convert a Rust closure to a C-style callback? - #11 by Yandros

Surely static mut is only dangerous if accessed from multiple threads? My GUI App object will only ever be accessed from the main thread.

Your code for converting a Rust closure to a C callback it too advanced for me to understand. So I'd only use something like that if it were packaged into a crate and had a simple API.

The reason that the two lines have to be in main rather than app is that variables introduced by let bindings are dropped at the end of the scope in which it is introduced. So:

fn app() -> &'static mut App {
    unsafe {
        if _APP.is_null() {
            let mut _app = App::new();
            unsafe { _APP = &mut _app; }
            // _app is dropped here, so _APP becomes dangling
        }
        &mut *_APP
    }
}
1 Like

More concerning than the static mut is the unguarded production of an &mut App from a raw pointer. Consider something like this:

let a: &mut App = app();
a.x = 27;
{  // probably in some called function
    let b: &mut App = app();
    println!("{}", b.x);
}

Because the compiler believes a to be exclusively held, it’s free to reorder the assignment of a.x until after the print statement— b’s reference is, as far as it’s concerned, to a different App.

(I suspect this example is faulty in some way, but I’m confident about the unsoundness. Hopefully someone will come by with a better example of the problem.)


A more sound version of your approach might look something like this:

use std::sync::Mutex;
use lazy_static::lazy_static;

impl App {
    fn init()->Self { /* ... */ }

    pub fn get()->impl std::ops::DerefMut<Target=App>{
        lazy_static! {
            static ref INSTANCE: Mutex<App> = Mutex::new(App::init());
        }
        INSTANCE.lock().expect("App already in use")
    }
}

(Playground)

2 Likes

I tried your approach but it doesn't work for me:

error[E0277]: `*mut iup::global::Ihandle` cannot be sent between threads safely
  --> src/app.rs:22:9
   |
22 | /         lazy_static! {
23 | |             static ref APP: Mutex<App> = Mutex::new(App::init());
24 | |         }
   | |_________^ `*mut iup::global::Ihandle` cannot be sent between threads safely
   | 
  ::: /home/mark/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 |   pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                      ---- required by this bound in `lazy_static::lazy::Lazy`
   |
   = help: within `app::App`, the trait `std::marker::Send` is not implemented for `*mut iup::global::Ihandle`
   = note: required because it appears within the type `dialog::Dialog`
   = note: required because it appears within the type `app::App`
   = note: required because of the requirements on the impl of `std::marker::Sync` for `std::sync::Mutex<app::App>`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

(Incidentally, Dialog is just a struct that wraps up some of the stuff originally in App.)

The irony is that I have no wish to pass any GUI stuff to secondary threads; it will all live in the main thread.

Right; I should’ve anticipated that— anything stored in a static needs to be thread-safe, even in single-threaded programs. You can do the same thing with thread_local! but it’s a bit less straightforward:

    pub fn get()->impl std::ops::DerefMut<Target=App>{
        thread_local! {
            static INSTANCE: &'static RefCell<App> =
                Box::leak(Box::new(RefCell::new(App::init())));
        }
        INSTANCE.with(|refcell| refcell.borrow_mut())
    }

Thanks, that allows the GUI to start -- but the callbacks then fail.
Here's the code that's what you suggested:

// main.rs
fn main() {
    App::get().run();
    IUP.close();
}
// app.rs
pub struct App {
    pub unsaved_changes: bool,
    pub dialog: Dialog,
}

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

    pub fn get() -> impl std::ops::DerefMut<Target=App> {
        thread_local! {
            static APP: &'static RefCell<App> =
                Box::leak(Box::new(RefCell::new(App::init())));
        }
        APP.with(|refcell| refcell.borrow_mut())
    }

And here's how I did the callbacks. Note that the GUI runs the widgets appear, but as soon as I click a button, it crashes (traceback after):

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

Here's the full traceback:

     Running `target/release/helloiup`
thread 'main' panicked at 'already borrowed: BorrowMutError', /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/cell.rs:877:9
stack backtrace:
   0:     0x560274389729 - backtrace::backtrace::libunwind::trace::h396c07d2071b43af
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/libunwind.rs:86
   1:     0x560274389729 - backtrace::backtrace::trace_unsynchronized::h7aa0e4bb23d9c158
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/mod.rs:66
   2:     0x560274389729 - std::sys_common::backtrace::_print_fmt::hd15ac5d4adcd355b
                               at src/libstd/sys_common/backtrace.rs:78
   3:     0x560274389729 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hec5354be8ccc3ecc
                               at src/libstd/sys_common/backtrace.rs:59
   4:     0x56027437aebc - core::fmt::write::h3d34909eeb4f225b
                               at src/libcore/fmt/mod.rs:1076
   5:     0x560274388c86 - std::io::Write::write_fmt::h1da287b3de55ed16
                               at src/libstd/io/mod.rs:1537
   6:     0x560274388470 - std::sys_common::backtrace::_print::h4d206838e1ace354
                               at src/libstd/sys_common/backtrace.rs:62
   7:     0x560274388470 - std::sys_common::backtrace::print::h1f778e9940ee5977
                               at src/libstd/sys_common/backtrace.rs:49
   8:     0x560274388470 - std::panicking::default_hook::{{closure}}::h704403a56cbf5783
                               at src/libstd/panicking.rs:198
   9:     0x5602743879a1 - std::panicking::default_hook::ha4567a10dec4ef8d
                               at src/libstd/panicking.rs:218
  10:     0x5602743879a1 - std::panicking::rust_panic_with_hook::h88a1f16ec8a7bb20
                               at src/libstd/panicking.rs:486
  11:     0x560274387698 - rust_begin_unwind
                               at src/libstd/panicking.rs:388
  12:     0x560274379710 - core::panicking::panic_fmt::hbddb7fe6f399b81a
                               at src/libcore/panicking.rs:101
  13:     0x56027437b472 - core::option::expect_none_failed::h60849c4323f09783
                               at src/libcore/option.rs:1272
  14:     0x560274376263 - helloiup::dialog::on_ok::h74279edcadb0afed
  15:     0x7f105aa89bd1 - gtkButtonClicked
  16:     0x7f1058747346 - <unknown>
  17:     0x7f10587629ff - g_signal_emit_valist
  18:     0x7f105876312f - g_signal_emit
  19:     0x7f1059749d5e - <unknown>
  20:     0x7f105984d8f7 - <unknown>
  21:     0x7f1058747250 - <unknown>
  22:     0x7f10587623cd - g_signal_emit_valist
  23:     0x7f105876312f - g_signal_emit
  24:     0x7f1059995534 - <unknown>
  25:     0x7f10599b5f0b - gtk_window_propagate_key_event
  26:     0x7f10599b5f61 - <unknown>
  27:     0x7f105984d8f7 - <unknown>
  28:     0x7f1058747346 - <unknown>
  29:     0x7f10587623cd - g_signal_emit_valist
  30:     0x7f105876312f - g_signal_emit
  31:     0x7f1059995534 - <unknown>
  32:     0x7f105984a93f - <unknown>
  33:     0x7f105984c948 - gtk_main_do_event
  34:     0x7f105935d765 - <unknown>
  35:     0x7f105938df92 - <unknown>
  36:     0x7f105846c417 - g_main_context_dispatch
  37:     0x7f105846c650 - <unknown>
  38:     0x7f105846c962 - g_main_loop_run
  39:     0x7f105984ba25 - gtk_main
  40:     0x7f105aa85414 - IupMainLoop
  41:     0x560274376c47 - helloiup::main::h69cb6aa56ecd4685
  42:     0x560274390d33 - std::rt::lang_start_internal::{{closure}}::{{closure}}::hb7d2131b13621efb
                               at src/libstd/rt.rs:52
  43:     0x560274390d33 - std::sys_common::backtrace::__rust_begin_short_backtrace::h4bc151ea7245ae33
                               at src/libstd/sys_common/backtrace.rs:130
  44:     0x5602743772ea - main
  45:     0x7f1059f45b97 - __libc_start_main
  46:     0x560274375d6a - _start
  47:                0x0 - <unknown>

This line is holding a mutable reference to App for the duration of the program, which prevents the callbacks from obtaining their own copy. What does the run() method look like?

In particular, this line should probably be App::run(), so that it can release the lock on App before it starts the event loop, which will obviously require some changes.

This is App::run(). self.dialog holds a Dialog which is a struct similar to App with *mut Ihandles for the widgets.

    pub fn run(&mut self) {
        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");
        }
    }

You need to not be holding a reference to App when you call IUP.main_loop(), so that it’s available for the callbacks to use:

pub fn run() {
    let app = Self::get();  // or App::get()
    app.dialog.build();
    if !IUP.show_xy(app.dialog.dialog, iup::MOUSEPOS, iup::MOUSEPOS) {
        std::mem::drop(app);
        IUP.main_loop();
        Self::get().save();
    } else {
        println!("Failed to show main window");
    }
}

That crashes with a shorter backtrace.

    pub fn run(&mut self) {
        let mut app = Self::get();
        app.dialog.build();
        if IUP.show_xy(app.dialog.dialog, iup::MOUSEPOS, iup::MOUSEPOS) {
            std::mem::drop(app);
            IUP.main_loop();
            Self::get().save();
        } else {
            println!("Failed to show main window");
        }
    }

Backtrace:

     Running `target/release/helloiup`
thread 'main' panicked at 'already borrowed: BorrowMutError', /rustc/5c1f21c3b82297671ad3ae1e8c942d2ca92e84f2/src/libcore/cell.rs:877:9
stack backtrace:
   0:     0x564efc2a37c9 - backtrace::backtrace::libunwind::trace::h396c07d2071b43af
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/libunwind.rs:86
   1:     0x564efc2a37c9 - backtrace::backtrace::trace_unsynchronized::h7aa0e4bb23d9c158
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.46/src/backtrace/mod.rs:66
   2:     0x564efc2a37c9 - std::sys_common::backtrace::_print_fmt::hd15ac5d4adcd355b
                               at src/libstd/sys_common/backtrace.rs:78
   3:     0x564efc2a37c9 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hec5354be8ccc3ecc
                               at src/libstd/sys_common/backtrace.rs:59
   4:     0x564efc294f5c - core::fmt::write::h3d34909eeb4f225b
                               at src/libcore/fmt/mod.rs:1076
   5:     0x564efc2a2d26 - std::io::Write::write_fmt::h1da287b3de55ed16
                               at src/libstd/io/mod.rs:1537
   6:     0x564efc2a2510 - std::sys_common::backtrace::_print::h4d206838e1ace354
                               at src/libstd/sys_common/backtrace.rs:62
   7:     0x564efc2a2510 - std::sys_common::backtrace::print::h1f778e9940ee5977
                               at src/libstd/sys_common/backtrace.rs:49
   8:     0x564efc2a2510 - std::panicking::default_hook::{{closure}}::h704403a56cbf5783
                               at src/libstd/panicking.rs:198
   9:     0x564efc2a1a41 - std::panicking::default_hook::ha4567a10dec4ef8d
                               at src/libstd/panicking.rs:218
  10:     0x564efc2a1a41 - std::panicking::rust_panic_with_hook::h88a1f16ec8a7bb20
                               at src/libstd/panicking.rs:486
  11:     0x564efc2a1738 - rust_begin_unwind
                               at src/libstd/panicking.rs:388
  12:     0x564efc2937b0 - core::panicking::panic_fmt::hbddb7fe6f399b81a
                               at src/libcore/panicking.rs:101
  13:     0x564efc295512 - core::option::expect_none_failed::h60849c4323f09783
                               at src/libcore/option.rs:1272
  14:     0x564efc290c1c - helloiup::main::h69cb6aa56ecd4685
  15:     0x564efc2aadd3 - std::rt::lang_start_internal::{{closure}}::{{closure}}::hb7d2131b13621efb
                               at src/libstd/rt.rs:52
  16:     0x564efc2aadd3 - std::sys_common::backtrace::__rust_begin_short_backtrace::h4bc151ea7245ae33
                               at src/libstd/sys_common/backtrace.rs:130
  17:     0x564efc29138a - main
  18:     0x7f2bb9aefb97 - __libc_start_main
  19:     0x564efc28fd6a - _start
  20:                0x0 - <unknown>

run shouldn’t take &mut self as a parameter anymore, because it gets its own reference via App::get(). It then gets called as App::run() instead of App::get().run().

I don't honestly understand it (esp. the mem::drop()), but having changed run's signature to pub fn run() everything now works, GUI, callbacks and all.

I've now reduced main to:

fn main() { App::run(); }

since I've now moved IUP.close() to the end of App::run().

Now I can move forward and see how far I can get.

Thanks you!

1 Like

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.