Passing mutable context into callbacks

2019-05-10 16:00发布

问题:

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).

回答1:

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)) {}


标签: rust lifetime