将可变上下文传递给回调

时间:2016-08-22 22:53:30

标签: rust lifetime

我试图在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)来使用它,该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!() } } 运行一个事件循环,直到它完成&#34; (比如在窗口小部件上按下一个按钮),然后控制权返回给调用者。

Lua状态的包装器,其中包括安全地获取指向Rust对象的指针:

Rc<RefCell<T>>

有一个智能指针(它只是一个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() } } )与传递给Lua的对象一起使用,因此Rust代码可以执行可变的操作,即使在Lua状态中存储了一个引用:

MyWidget

最后有MyWidget,它应该是允许Lua代码实现小部件的垫片,这就是我目前的困难所在。我们的想法是:

  • MyWidget确实需要(可变)访问Lua状态,例如能够调用Lua回调。
  • &mut由于一般UI::run别名规则(显然在许多其他地方使用过),因此无法存储对Lua状态的可变引用。
  • 因此,我需要将Lua状态传递给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); } } 参数)。
fn main() {
    let mut rl = RL{marker: PhantomData};
    rl.register(("l_run", MyWidget::l_run));
}

最后,需要注册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) {
   |>     ^

Play link

目前的尝试导致:

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

但是,如果我接受编译器的建议并添加显式生命周期参数,则该函数不再与注册时所需的签名相匹配,而是获取:

<script type="text/javascript">
var x = document.getElementById("demo");

function getLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showPosition, showError);
    } else { 
        x.innerHTML = "Geolocation is not supported by this browser.";
    }
}

function showPosition(position) {
    x.innerHTML = "Latitude: " + position.coords.latitude + 
    "<br>Longitude: " + position.coords.longitude;
}

function showError(error) {
    switch(error.code) {
        case error.PERMISSION_DENIED:
            x.innerHTML = "User denied the request for Geolocation."
            break;
        case error.POSITION_UNAVAILABLE:
            x.innerHTML = "Location information is unavailable."
            break;
        case error.TIMEOUT:
            x.innerHTML = "The request to get user location timed out."
            break;
        case error.UNKNOWN_ERROR:
            x.innerHTML = "An unknown error occurred."
            break;
    }
}

navigator.geolocation.getCurrentPosition(success, error);
    function success(position) {
        var GEOCODING = 'https://maps.googleapis.com/maps/api/geocode/json?latlng=' + position.coords.latitude + '%2C' + position.coords.longitude + '&language=en';
        $.getJSON(GEOCODING).done(function(location) {
             $('#latitude').html(position.coords.latitude);
             $('#longitude').html(position.coords.longitude);
               $.ajax({
                   url:'table.php',
                   method: 'POST',
                   dataType: 'json',
                   data:{
                       lat: $('#latitude').val(),
                       lng: $('#longitude').val()
                   },
                   success: function(data){
                       console.log();
                   }
                });             
             }
             function error(err) {
                 console.log(err)
             }
</script>

因此修复上一个错误意味着签名不再与注册函数兼容(这不是通用的;实际上我一次性传递具有多个函数的切片)。

1 个答案:

答案 0 :(得分:2)

这些深刻的终身问题很多,所以让我们看看我们是否可以解决这个问题。让我们先来看一下具有相同错误的代码的精简版本:

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