将可变上下文传递给回调 [英] Passing mutable context into callbacks
问题描述
我正在尝试在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
运行器是通用的,可以在不涉及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
由于通用的&mut
别名规则(显然在许多其他地方使用),因此无法存储对Lua状态的可变引用. - 因此,我需要将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))
因此,更正先前的错误意味着签名不再与注册功能兼容(这不是通用功能;实际上,我一次性通过了具有多个功能的切片).
这些深层的生命周期问题涉及很多方面,因此让我们看看是否可以解决.让我们从具有相同错误的精简代码开始:
struct RunLoop<'a> {
marker: &'a u8,
}
struct MyContext<'a> {
rl: &'a mut RunLoop<'a>,
}
fn run(rl: &mut RunLoop) {
let mut ctxt = MyContext { rl: rl };
}
fn main() {}
MyContext
的定义指出需要为其提供对RunLoop
的引用. RunLoop
的生存期和RunLoop
的生存期需要统一设置-它们都设置为'a
.但是,基于run
的签名不能保证这一点.所知道的是,有两个生命周期,目前都消失了.
这导致了一个解决方案:我们可以显式地标识这两个生存期并在它们之间建立关系:
struct MyContext<'a, 'b : 'a> {
rl: &'a mut RunLoop<'b>,
}
另一种解决方案是编译器提示的解决方案:在调用run
时预先统一生存期:
fn run<'a>(rl: &'a mut RunLoop<'a>) {
但是,后一种解决方案在较大的程序中不起作用,失败:
error: mismatched types [--explain E0308]
--> src/main.rs: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(&'a mut RL<'a>) {MyWidget::l_run}`
note: expected concrete lifetime is lifetime ReSkolemized(0, BrAnon(0))
(附带说明:自从我在错误消息中看到ReSkolemized
提及以来,已经很久了 !)
让我们扩展这个小例子以产生相同的错误:
struct RunLoop<'a> {
marker: &'a u8,
}
struct MyContext<'a> {
rl: &'a mut RunLoop<'a>,
}
fn run<'a>(rl: &'a mut RunLoop<'a>) {
let mut ctxt = MyContext { rl: rl };
}
fn register(func: fn(&mut RunLoop)) {}
fn main() {
register(run);
}
我不太确定.我确实知道,将任何显式生存期都放在引用上可以帮助它进行编译:
fn register<'a>(func: fn(&'a mut RunLoop<'a>)) {}
fn register<'a, 'b>(func: fn(&'a mut RunLoop<'b>)) {}
fn register(func: fn(&'static mut RunLoop)) {}
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));
}
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).
These deep lifetime issues are hairy, so let's see if we can figure it out. Let's start with a look at a stripped-down version of the code that has the same error:
struct RunLoop<'a> {
marker: &'a u8,
}
struct MyContext<'a> {
rl: &'a mut RunLoop<'a>,
}
fn run(rl: &mut RunLoop) {
let mut ctxt = MyContext { rl: rl };
}
fn main() {}
The definition of MyContext
states that it needs to be provided a reference to a RunLoop
. The lifetime of the RunLoop
and the lifetime the RunLoop
is parameterized with need to be unified - they are both set to 'a
. However, this cannot be guaranteed based on the signature of run
. All that is known is that there are two lifetimes, both elided at the moment.
This leads to one solution: we can explicitly identify both lifetimes and establish a relationship between them:
struct MyContext<'a, 'b : 'a> {
rl: &'a mut RunLoop<'b>,
}
Another solution is the one hinted at by the compiler: pre-unify the lifetimes when run
has been called:
fn run<'a>(rl: &'a mut RunLoop<'a>) {
However, this latter solution doesn't work in the larger program, failing with:
error: mismatched types [--explain E0308]
--> src/main.rs: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(&'a mut RL<'a>) {MyWidget::l_run}`
note: expected concrete lifetime is lifetime ReSkolemized(0, BrAnon(0))
(Side note: it's been a long time since I've seen a ReSkolemized
mention in an error message!)
Let's extend our small example to generate the same error:
struct RunLoop<'a> {
marker: &'a u8,
}
struct MyContext<'a> {
rl: &'a mut RunLoop<'a>,
}
fn run<'a>(rl: &'a mut RunLoop<'a>) {
let mut ctxt = MyContext { rl: rl };
}
fn register(func: fn(&mut RunLoop)) {}
fn main() {
register(run);
}
This I'm less sure about. I do know that putting just about any explicit lifetime on the reference helps it compile:
fn register<'a>(func: fn(&'a mut RunLoop<'a>)) {}
fn register<'a, 'b>(func: fn(&'a mut RunLoop<'b>)) {}
fn register(func: fn(&'static mut RunLoop)) {}
这篇关于将可变上下文传递给回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!