Having trouble integrating Lua scripting into a GTK4 app

I’m trying to write an app using gtk-rs, and I want to integrate Lua scripting into it, but I’m struggling to get the lifetimes to cooperate. The primary issue is that callback closures require a 'static lifetime, which the Lua library is unable to provide due to being initialized at runtime. It looks like this has been discussed before, but with no solution.

As an MVP, here is my Cargo.toml:

[package]                                                                                                                                                                                                                                     
name = "gtk-lua"                                                                                                                                                                                                                              
version = "0.1.0"                                                                                                                                                                                                                             
edition = "2021"                                                                                                                                                                                                                              
                                                                                                                                                                                                                                              
[dependencies]                                                                                                                                                                                                                                
gtk = { version = "0.4", package = "gtk4" }                                                                                                                                                                                                   
mlua = { version = "0.7", features = ["lua54"] }

And my main.rs:

use gtk::prelude::*;                                                                                                                                                                                                                          
use gtk::{Application, ApplicationWindow};                                                                                                                                                                                                    
                                                                                                                                                                                                                                              
fn main() {                                                                                                                                                                                                                                   
    // Create a new application                                                                                                                                                                                                               
    let app = Application::builder()                                                                                                                                                                                                          
        .application_id("org.gtk-rs.example")                                                                                                                                                                                                 
        .build();                                                                                                                                                                                                                             
                                                                                                                                                                                                                                              
    let lua = mlua::Lua::new();                                                                                                                                                                                                               
                                                                                                                                                                                                                                              
    // Connect to "activate" signal of `app`                                                                                                                                                                                                  
    app.connect_activate(|app| build_ui(&app, &lua));                                                                                                                                                                                         
                                                                                                                                                                                                                                              
    // Run the application                                                                                                                                                                                                                    
    app.run();                                                                                                                                                                                                                                
}                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                              
fn build_ui(app: &Application, lua: &mlua::Lua) {                                                                                                                                                                                             
    // Create a window and set the title                                                                                                                                                                                                      
    let window = ApplicationWindow::builder()                                                                                                                                                                                                 
        .application(app)                                                                                                                                                                                                                     
        .title("My GTK App")                                                                                                                                                                                                                  
        .build();                                                                                                                                                                                                                             
                                                                                                                                                                                                                                              
    let activate_focus_callback = lua.create_function(|_, ()| {                                                                                                                                                                               
        println!("Activate focus signal received");                                                                                                                                                                                           
        Ok(())                                                                                                                                                                                                                                
    }).unwrap();                                                                                                                                                                                                                              
                                                                                                                                                                                                                                              
    window.connect_activate_focus(move |_| {                                                                                                                                                                                                  
        activate_focus_callback.call::<(), ()>(());                                                                                                                                                                                           
    });                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                              
    // Present window                                                                                                                                                                                                                         
    window.present();                                                                                                                                                                                                                         
} 

This is basically just the Hello World app, but with a Lua function defined as a callback on the window. When compiled, I get this error message:

   Compiling gtk-lua v0.1.0 (/home/damien/Workspace/gtk-lua-test)
error[E0759]: `lua` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:26:39
   |
19 | fn build_ui(app: &Application, lua: &mlua::Lua) {
   |                                     ---------- this data with an anonymous lifetime `'_`...
...
26 |     let activate_focus_callback = lua.create_function(|_, ()| {
   |                                   --- ^^^^^^^^^^^^^^^
   |                                   |
   |                                   ...is captured here...
...
31 |     window.connect_activate_focus(move |_| {
   |            ---------------------- ...and is required to live as long as `'static` here

For more information about this error, try `rustc --explain E0759`.
error: could not compile `gtk-lua` due to previous error

Is there any way to be able to invoke a Lua-defined function within a GTK callback? I know that callbacks can basically happen whenever, and I need to promise to the compiler that Lua is still around, but it seems like something that should at least be doable with unsafe code, and while I can get it to compile, the result just segfaults.

Any tips on how to get this to work?

Hello, you could try to put Lua in an Rc and pass an owned copy of that into the callback closure. I think you would also need to make a self-referential struct to hold the function and the Rc. Also I think you could put it in a static OnceCell too if you wanted to avoid all that.

Actually, that example isn’t great. What I’m currently struggling with is how to invoke a Lua callback from a glib signal handler. Here’s a better example:

use gtk::prelude::*;
use mlua::{UserData, UserDataMethods};

fn main() {}

struct Widget(gtk::Widget);

impl UserData for Widget {
    fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_method("connect", |_, this, (signal, callback): (String, mlua::Function)| {
            this.0.connect_local(&signal, false, move |_| {
                callback.call::<(), ()>(());
                None
            });
            Ok(())
        });
    }
}

Compiling this gives this lengthy error:

   Compiling gtk-lua v0.1.0 (/home/damien/Workspace/gtk-lua-test)
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'lua` due to conflicting requirements
    --> src/main.rs:10:17
     |
10   |         methods.add_method("connect", |_, this, (signal, callback): (String, mlua::Function)| {
     |                 ^^^^^^^^^^
     |
note: first, the lifetime cannot outlive the lifetime `'lua` as defined here...
    --> src/main.rs:9:20
     |
9    |     fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
     |                    ^^^^
note: ...so that the types are compatible
    --> src/main.rs:10:17
     |
10   |         methods.add_method("connect", |_, this, (signal, callback): (String, mlua::Function)| {
     |                 ^^^^^^^^^^
     = note: expected `FromLuaMulti<'lua>`
                found `FromLuaMulti<'_>`
     = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:11:50: 14:14]` will meet its required lifetime bounds...
    --> src/main.rs:11:20
     |
11   |             this.0.connect_local(&signal, false, move |_| {
     |                    ^^^^^^^^^^^^^
note: ...that is required by this bound
    --> /home/damien/.asdf/installs/rust/1.58.1/registry/src/github.com-1ecc6299db9ec823/glib-0.15.10/src/object.rs:1723:44
     |
1723 |         F: Fn(&[Value]) -> Option<Value> + 'static;
     |                                            ^^^^^^^

For more information about this error, try `rustc --explain E0495`.
error: could not compile `gtk-lua` due to previous error

What I really need to do is somehow lift the callback mlua::Function from its 'lua lifetime, and give it a 'static one. Supposedly, this is what Box::leak() does, but I can’t even get that to compile.

Upon reading the mlua documentation, it seems the suggested way is to use a RegistryKey and store that in the closure. See more info here: Can't save LuaFunction · Issue #62 · khvzak/mlua · GitHub

Ah, nice find! That definitely looks promising, I’ll have to give it a look soon.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.