我正在尝试用 Rust 构建一个简单的 UI,但部分可以在 Lua 中编写脚本,使用 rust-lua53,并且在找到一种让 Lua 组件访问 Lua 状态的好方法时遇到问题。这个问题/示例比我想要的要长一点,抱歉!
UI 的核心是Widget
具有在按下按键或应重绘屏幕/窗口时调用的方法的特征。类型参数C
是我想要传递的上下文(见下文)。
trait Widget<C> {
fn handle_key<'c>(&mut self, key: char, context: &'c mut C);
}
有一个UI
处理事件循环、读取键并调用的结构draw
方法(问题中省略)。这Widget
特质和UI
runner 是通用的,可以在没有任何 Lua 相关内容的情况下使用。
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>
{
}
}
您可以通过实施来使用它Widget
并打电话ui.run(widget)
,它运行一个事件循环,直到它“完成”(比如按下小部件上的按钮),并将控制权返回给调用者。
Lua 状态有一个包装器,它(除其他外)处理安全地获取指向 Rust 对象的指针:
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!() }
}
有一个智能指针(它只是一个Rc<RefCell<T>>
) 与传递给 Lua 的对象一起使用,这样即使 Lua 状态中隐藏了一个引用,Rust 代码也可以执行可变的操作:
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()
}
}
最后还有MyWidget
,它应该是一个垫片,允许Lua代码实现小部件,这就是我当前的困难所在。想法是:
-
MyWidget
确实需要(可变)访问 Lua 状态,例如能够调用 Lua 回调。
-
MyWidget
由于一般原因,无法存储对 Lua 状态的可变引用&mut
别名规则(显然它在许多其他地方使用)。
- 因此我需要将Lua状态传递给
UI::run
并继续到Widget
方法(因此添加C
参数如上)。
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);
}
}
最后,需要注册 l_run 方法:
fn main() {
let mut rl = RL{marker: PhantomData};
rl.register(("l_run", MyWidget::l_run));
}
当前的尝试结果是:
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) {
|> ^
但是,如果我接受编译器的建议并添加显式生命周期参数,则该函数不再与注册时所需的签名匹配,而是得到:
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))
因此,修复之前的错误意味着签名不再与注册函数兼容(这不是通用的;实际上,我一次性传递了具有多个函数的切片)。