不可变上下文的不安全变异在发布模式下失败

问题描述 投票:0回答:1

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

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

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

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

std::mem::replace()

std::mem::swap()

  1. 这是什么原因?
  2. 在 --release 模式下我想做的事情是不可能的吗?
rust unsafe
1个回答
1
投票

Rust 不允许你改变从不可变引用中获得的值,即使你将它转换为可变引用或可变原始指针。允许编译器假设使用不可变引用传递的值永远不会改变,并且它可以在考虑到这一点的情况下进行优化。打破这个假设是未定义的行为,并且会以意想不到的方式破坏你的程序。这可能是您的示例中发生的情况:编译器看到

increment
采用不可变引用,因此
item
在调用前后必须相同,并且它可以优化代码,就好像是这种情况一样。

只有一个例外:

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

虽然

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

Cell
,例如,可以为你的例子工作:

    #[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());
}
© www.soinside.com 2019 - 2024. All rights reserved.