gpt4 book ai didi

rust - 将可变上下文传递给回调

转载 作者:行者123 更新时间:2023-11-29 08:00:32 25 4
gpt4 key购买 nike

我正在尝试在 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代码实现widgets的shim,而这正是我目前的难点所在。思路是:

  • 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));
}

Play link

当前尝试的结果是:

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

关于rust - 将可变上下文传递给回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39089905/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com