rust 为什么如果我借用了一个不可变的引用,然后我借用了它作为可变的代码仍然编译?[副本]

hgb9j2n6  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(90)

此问题已在此处有答案

What are non-lexical lifetimes?(1个答案)
10天前关门了。
以下代码无法编译(如预期):

use sdl2::event::Event;
use sdl2::pixels::Color;

pub fn main() {
    let sdl_context = sdl2::init().unwrap();
    let video_subsystem = sdl_context.video().unwrap();

    let window = video_subsystem
        .window("rust-sdl2 demo", 800, 600)
        .build()
        .unwrap();

    let mut canvas = window.into_canvas().build().unwrap();
    canvas.set_draw_color(Color::RGB(0, 0, 0));
    canvas.clear();
    canvas.present();

    let mut event_pump = sdl_context.event_pump().unwrap();
    let keyboard = sdl2::keyboard::KeyboardState::new(&event_pump);

    'running: loop {
        if keyboard.is_scancode_pressed(sdl2::keyboard::Scancode::Space) {
            println!("test");
        }
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. } => break 'running,
                _ => {}
            }
        }
    }
}

字符串
编译器显示以下错误消息:

error[E0502]: cannot borrow `event_pump` as mutable because it is also borrowed as immutable
  --> src\main.rs:25:22
   |
19 |     let keyboard = sdl2::keyboard::KeyboardState::new(&event_pump);
   |                                                       ----------- immutable borrow occurs here
...
22 |         if keyboard.is_scancode_pressed(sdl2::keyboard::Scancode::Space) {
   |            ------------------------------------------------------------- immutable borrow later used here
...
25 |         for event in event_pump.poll_iter() {
   |                      ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here


如果我把let keyboard行移到循环中,为什么它会编译?我想这可能与物体的寿命有关。但它显示的错误对我来说没有意义。根据我的理解,我不明白let keyboard在循环外部还是内部有什么关系。

46scxncf

46scxncf1#

Rust最好尝试看看它是否能找到一种方法来为您确定对象的范围,以便在函数体中不违反生存期。在循环中包含let keyboard时,Rust会查看它可以通过哪些有效方式来控制拖放顺序,并发现如下可能性:

'running: loop {
    {
        let keyboard = sdl2::keyboard::KeyboardState::new(&event_pump);
        if keyboard.is_scancode_pressed(sdl2::keyboard::Scancode::Space) {
            println!("test");
        }
    }
    for event in event_pump.poll_iter() {
        match event {
            Event::Quit { .. } => break 'running,
            _ => {}
        }
    }
}

字符串
换句话说,它注意到,如果你每次通过循环调用let,你每次都在有效地丢弃和重新分配键盘。然后,它继续注意到,如果它命令drop在你稍后去改变引用所指向的内容之前,去掉存储在键盘内部的对事件循环的不可变引用,那么它在语义上等同于你所写的内容。这样可以满足引用的要求,并且不会与您编写的实现冲突,因此编译器会自动替换它,以便您的代码可以编译,但它仍然是完全内存安全的。
这使得让let在循环之外的问题变得明显,因为keyboard都是1。不丢弃和2。在内部存储了对event_pump的不可变引用,因此它无法插入任何可能的删除顺序来确保内存安全,因为不再有删除操作。keyboard::new(约定)的函数签名意味着只要keyboard保持活动,就不能对event_pump进行变异。
如果在/match的事件之后再次使用keyboard,那么它将失败,因为它需要保持不可变的引用。
因此,基本上,编译器将“人工”作用域放置在有意义的地方,以便能够满足自己的规则,这样程序员就不必手动操作了。
只要可观察到的结果与您所写的完全等价,Rust就可以对操作重新排序,几乎所有的编译语言都是这样做的。操作之间的延迟不被认为是“可观察到的”,因此释放通常是允许移动的。这是允许人工作用域工作的基础属性。
作为一个历史记录,2015年 rust 没有这样做。Rust的最初版本使用了“词法生存期”借用检查器,但它不具备此功能。借用检查器在2018年版Rust中得到了“非词法生存期”检查器的大幅升级,不再严格依赖于标记的词法顺序,因此它可以进行这种类型的替换。

相关问题