I'm trying to build a simple UI in Rust, but partly scriptable in Lua, using rust-lua53, and having problems working out a good way to give Lua components access to the Lua state. This question/example is a little longer than I'd like, sorry!
The core of the UI is a Widget
trait with methods to call when keys are pressed or when the screen/window should be redrawn. The type parameter C
is the context I'll want to pass in (see later).
trait Widget<C> {
fn handle_key<'c>(&mut self, key: char, context: &'c mut C);
}
There's a UI
struct which handles the event loop, reading keys and calling the draw
method (left out for the question). The Widget
trait and UI
runner are generic and can be used without anything Lua-related.
struct UI {}
impl UI {
pub fn run<'c, 'm, T, C>(&mut self, widget: 'm T, context: &'c mut C)
where C: 'c, T: Widget<C>
{
}
}
You use it by implementing Widget
and calling ui.run(widget)
, which runs an event loop until it's "finished" (say a button is pressed on the widget), and control is returned to the caller.
There's a wrapper around the Lua state, which (amongst other things) handles safely getting pointers to Rust objects out:
struct RL<'a> {
marker: PhantomData<(&'a ())>,
}
impl<'a> RL<'a> {
pub fn get<T>(&mut self) -> Option<Ptr<T>>
where T: Any
{ unimplemented!() }
pub fn register(&mut self, func: (&'static str, fn(&mut RL) -> ()))
{ unimplemented!() }
}
There's a smart pointer (which is just an Rc<RefCell<T>>
) used with objects passed to Lua, so that Rust code can do mutable things even if there's a reference stashed away in the Lua state:
struct Ptr<T> {
obj: Rc<RefCell<T>>,
}
impl<T> Clone for Ptr<T> {
fn clone(&self) -> Self {
Ptr{ obj: self.obj.clone() }
}
}
impl<T> Ptr<T> {
pub fn borrow_mut<'a>(&'a mut self) -> RefMut<'a, T> where T:'a {
(*self.obj).borrow_mut()
}
}
And finally there's MyWidget
, which is supposed to be a shim to allow Lua code to implement widgets, and this is where my current difficulty is. The thinking is that:
MyWidget
does need (mutable) access to the Lua state, for example to be able to call Lua callbacks.MyWidget
can't store a mutable reference to the Lua state, due to general&mut
aliasing rules (it's obviously used in many other places).- Therefore I need to pass the Lua state into
UI::run
and on to theWidget
methods (hence adding theC
parameter above).
struct MyWidget {}
struct MyContext<'a> {
rl: &'a mut RL, // mutable reference to the Lua state
}
impl<'b> Widget<MyContext<'b>> for MyWidget {
fn handle_key(&mut self, key: char, context: &mut MyContext) {
unimplemented!()
}
}
impl MyWidget {
// This static method is called from Lua, where `MyWidget` has been made available as a userdata.
pub fn l_run(rl: &mut RL) {
// First get a Rust pointer to the widget out of the Lua state
let mut ui: Ptr<MyWidget> = rl.get().unwrap();
// Create a fresh UI runner
let mut rui = UI{};
// Make the context including the Lua state
let mut ctxt: MyContext = MyContext { rl: rl, };
// Run the widget, passing the context.
rui.run(&mut *ui.borrow_mut(), &mut ctxt);
}
}
And finally, the l_run method needs to be registered:
fn main() {
let mut rl = RL{marker: PhantomData};
rl.register(("l_run", MyWidget::l_run));
}
Play link
The current attempt results in:
error: cannot infer an appropriate lifetime due to conflicting requirements [E0495]
--> <anon>:57:35
|>
57 |> let mut ctxt: MyContext = MyContext { rl: rl, };
|> ^^^^^^^^^
help: consider using an explicit lifetime parameter as shown: fn l_run<'a>(rl: &'a mut RL<'a>)
--> <anon>:53:5
|>
53 |> pub fn l_run(rl: & mut RL) {
|> ^
But if I take the compiler's advice and add the explicit lifetime parameter, the function no longer matches the signature needed when registering, and I instead get:
error: mismatched types [--explain E0308]
--> <anon>:74:27
|>
74 |> rl.register(("l_run", MyWidget::l_run));
|> ^^^^^^^^^^^^^^^ expected concrete lifetime, found bound lifetime parameter
note: expected type `fn(&mut RL<'_>)`
note: found type `fn(&'r mut RL<'r>) {MyWidget::l_run}`
note: expected concrete lifetime is lifetime ReSkolemized(0, BrAnon(0))
So fixing the previous error means the signature is no longer compatible with the registration function (which isn't generic; in reality I pass in slices with several functions in one go).