将可变上下文传递给回调 [英] Passing mutable context into callbacks

查看:63
本文介绍了将可变上下文传递给回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在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 the Widget methods (hence adding the C 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).

解决方案

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆