在发布模式下,从不可变上下文进行不安全的变异会失败

Isa*_*kum -3 unsafe rust

在下面的代码中,我使用不安全代码将不可变引用转换为可变指针,然后尝试通过该可变指针编辑内部值。

fn main() {
    #[repr(transparent)]
    struct Item(isize);

    impl Item {
        #[inline]
        fn ptr(&self) -> *mut isize {
            self as *const Item as *mut isize
        }
        fn increment(&self) {
            let val = self.0 + 1;
            unsafe {std::ptr::write(self.ptr(), val)}
        }
    }

    let item = Item(22);
    println!("before = {}", item.0);
    item.increment();
    println!("after = {}", item.0);
}
Run Code Online (Sandbox Code Playgroud)

当我在调试模式下编译它时,结果符合预期,并且值确实增加了。然而,在发布模式下,尽管该部分代码运行,但该值根本不增加。此外,以下其他类型的值变异似乎也不起作用

unsafe {*&mut *(self.ptr()) += 1;}

std::mem::replace()

std::mem::swap()

  1. 这是什么原因呢?
  2. 我想要做的事情在 --release 模式下是不可能的吗?

Frx*_*rem 5

Rust 不允许您改变从不可变引用获得的值,即使您将其转换为可变引用或可变原始指针。编译器可以假设使用不可变引用传递的值永远不会改变,并且可以考虑到这一点进行优化。打破这个假设是未定义的行为,并且可能会以非常意想不到的方式破坏你的程序。这可能是您的示例中发生的情况:编译器认为它increment采用不可变引用,因此item在调用之前和之后必须相同,并且它可以优化代码,就好像这种情况一样。

只有一个例外:UnsafeCellUnsafeCell是一种特殊类型,允许内部可变性,让您&mut T从 an &UnsafeCell<T>(使用不安全代码)获取 a ,并告诉编译器它不能假设内容不变,即使您对单元格有不可变的引用。

但问题在于UnsafeCell,您仍然需要维护 Rust 的借用保证(例如,可变引用在存在时必须是唯一的),但编译器将依赖您作为程序员来确保这些保证得到维护。因此,通常建议您使用安全类型,例如CellRefCellMutex原子RwLock类型。这些都有不同的权衡,但它们都是作为安全包装器构建的,以允许内部可变性,同时对借用保证进行一些检查(一些在编译时,一些在运行时)。UnsafeCell

Cell例如,可以适用于您的示例:

fn main() {
    #[repr(transparent)]
    struct Item(Cell<isize>);

    impl Item {
        fn increment(&self) {
            let val = self.0.get() + 1;
            self.0.set(val);
        }
    }

    let item = Item(Cell::new(22));
    println!("before = {}", item.0.get());
    item.increment();
    println!("after = {}", item.0.get());
}
Run Code Online (Sandbox Code Playgroud)