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:
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.
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.
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)
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.
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.
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
}
}
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:
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:
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.
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");
}
}
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 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");
}
}
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.