当引用不再被借用时,Rust不会得到它

xeufq47z  于 2022-12-19  发布在  其他
关注(0)|答案(2)|浏览(124)

在Rust中,当我借用一个值时,编译器会注意到,但当我替换它时,编译器不会注意到,并发出E0597错误。
给定一个包含引用x的可变变量,当我用对局部变量的引用替换它的内容时,在局部变量超出作用域之前,我把它替换回原来的。
下面是一段代码:

struct X {payload : i32}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x = &pl;
        {
            let inner = X{payload : 30};
            let tmp = std::mem::replace(&mut x, &inner);
            println! ("data ={:?}", x.payload);
            let _f = std::mem::replace(&mut x, &tmp);
        }
        println! ("data ={:?}", x.payload);
    }
}

错误为:

error[E0597]: `inner` does not live long enough
  --> src/main.rs:9:49
   |
9  |             let tmp = std::mem::replace(&mut x, &inner);
   |                                                 ^^^^^^ borrowed value does not live long enough
...
12 |         }
   |         - `inner` dropped here while still borrowed
13 |         println! ("data ={:?}", x.payload);
   |                                 --------- borrow later used here

For more information about this error, try `rustc --explain E0597`.

编译器注意到我将inner的引用赋给x,但忽略了这样一个事实,即当inner仍然有效时,我再次将此引用替换为对pl的原始引用。
预期输出应为:

data =30
data =44

我哪里做错了?

brjng4g3

brjng4g31#

我已经解决了这个问题。不幸的是,它是一个编译器错误或限制。
语义上等价的代码,代码的学分属于发布了部分答案的人,但随后将其删除。

// This one will yields error E0597
struct X {payload : i32}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x = &pl;
        println! ("data ={:?}", x.payload);
        {
            let inner = X{payload : 30};
            let tmp : &X = x;
            x = &inner;
            println! ("data ={:?}", x.payload);
            x = tmp;
        }
        println! ("data ={:?}", x.payload);
    }
}

此代码将产生相同的错误。
然而,一个小的调整将使它编译没有错误或警告。

// Compiles without errors/warnings.
struct X {payload : i32}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x = &pl;
        println! ("data ={:?}", x.payload);
        {
            let inner = X{payload : 30};
            x = &inner;
            println! ("data ={:?}", x.payload);
            x = &pl;
        }
        println! ("data ={:?}", x.payload);
    }
}

这让我相信有一个编译器bug,因为现在编译器发现inner的生存期与x的生存期解耦了。
当你把内部代码块放到一个单独的函数中时,问题又回来了。所以这只是一个例子,Rust编译器有一些优化代码路径,捕捉到了极端情况。

// Yields error E0597 again.
struct X {payload : i32}

fn inner_func(x : &mut &X) {
    let inner = X{payload : 30};
    let tmp : &X = *x;
    *x = &inner;
    println! ("data ={:?}", (*x).payload);
    *x = &tmp;
}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x : &X = &pl;
        inner_func(&mut &mut x);
        println! ("data ={:?}", x.payload);
    }
}

Frederico提供了一种方法,可以将“无尽”的生命周期硬塞到inner的生命周期中,使其更大,即使这意味着使用不安全的生命周期。

// This one compiles without errors/warnings.
struct X {payload : i32}

fn inner_func(x : &mut &X) {
    let inner = X{payload : 30};
    let tmp : &X = *x;
    unsafe {
        *x = std::mem::transmute::<_, &'static X>(&inner);
    }
    println! ("data ={:?}", (*x).payload);
    *x = &tmp;
}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x : &X = &pl;
        inner_func(&mut &mut x);
        println! ("data ={:?}", x.payload);
    }
}
8i9zcol2

8i9zcol22#

当分析Rust代码时,编译器是保守的,并且拒绝它不能确定是否存在内存错误的程序。
特别是,当编译器检查代码中的let tmp = ...时,它会注意到您正在存储一个指向短期示例的引用(inner)转换为引用在 * inner之后 * 使用的(pl)超出范围。编译器在此停止并报告错误,说你 * 可能 * 创建了一个悬空引用。它没有考虑到在你恢复原始的pl之后。目前用于分析的生存期抽象不够精确。
在Rust中,你必须构造你的代码,这样编译器才能轻松地检查它,这意味着,比如,你必须在外部作用域声明inner,或者你使用Rc这样的类型来代替引用,它用库中执行的运行时检查来代替编译时检查。
如果你真的想使用不安全的代码,你可以按照下面的方法来做,但这是非常不建议的。你可以手动检查代码是否有未定义的行为,这是很难做到的,因为目前还没有完整的描述什么是未定义的行为。

**警告:**下面的列表并不详尽。对于不安全代码中允许和不允许的行为,Rust的语义没有正式模型,因此可能有更多的行为被认为是不安全的。下面的列表只是我们确定的未定义行为。请在编写不安全代码之前阅读Rustonomicon

struct X {payload : i32}

fn main() {
    let pl = X{payload : 44};
    {
        let mut x = &pl;
        // SAFETY: ...(motivate why the following is ok)
        unsafe {
            let inner = X{payload : 30};
            x = std::mem::transmute::<_, &'static X>(&inner);
            println! ("data ={:?}", x.payload);
            x = &pl;
        }
        println! ("data ={:?}", x.payload);
    }
}

相关问题