我正在try 实现Copy值类型的内部可变性(用于缓存目的).

问题是,据我所知,没有一个类型可用于内部可变性(例如UnsafeCell和相关类型,原子类型)允许Copy trait.This is stable Rust btw.

我的问题是:在这种情况下,使用std::ptr::write_volatilestd::ptr::read_volatile实现内部可变性有多安全?

具体地说,我有这段代码,我想知道在这段代码中是否有任何错误或未定义的行为:

use std::fmt::{Debug, Display, Formatter};

#[derive(Copy, Clone)]
pub struct CopyCell<T: Copy> {
    value: T,
}

impl<T: Copy> CopyCell<T> {
    pub fn new(value: T) -> Self {
        Self { value }
    }

    pub fn get(&self) -> T {
        unsafe { std::ptr::read_volatile(&self.value) }
    }

    pub fn set(&self, value: T) {
        let ptr = &self.value as *const T;
        let ptr = ptr as *mut T;
        unsafe { std::ptr::write_volatile(ptr, value) }
    }
}

impl<T: Default + Copy> Default for CopyCell<T> {
    fn default() -> Self {
        CopyCell { value: T::default() }
    }
}

impl<T: Display + Copy> Display for CopyCell<T> {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        let value = self.get();
        write!(f, "{value}")
    }
}

impl<T: Debug + Copy> Debug for CopyCell<T> {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        let value = self.get();
        write!(f, "{value:?}")
    }
}

我的目标是使用它在值类型中实现本地缓存(例如,一种记忆).我特别想要Copy语义(即,而不是Clone),因为他们有更好的人体工程学为我试图实现.

以下是一个用法示例:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_test() {
        let a = CopyCell::default();
        let b = CopyCell::new(42);

        assert_eq!(a.get(), 0);
        assert_eq!(b.get(), 42);

        a.set(123);
        b.set(b.get() * 10);
        assert_eq!(a.get(), 123);
        assert_eq!(b.get(), 420);

        let a1 = a;
        let b1 = b;

        a.set(0);
        b.set(0);

        assert_eq!(a1.get(), 123);
        assert_eq!(b1.get(), 420);
    }

    #[test]
    fn cached_compute() {
        let a = CachedCompute::new(10);
        assert_eq!(a.compute(), 100);
        assert_eq!(a.compute(), 100);

        let b = a;
        assert_eq!(b.compute(), 100);
    }

    #[derive(Copy, Clone)]
    pub struct CachedCompute {
        source: i32,
        result: CopyCell<Option<i32>>,
    }

    impl CachedCompute {
        pub fn new(source: i32) -> Self {
            Self {
                source,
                result: Default::default(),
            }
        }

        pub fn compute(&self) -> i32 {
            if let Some(value) = self.result.get() {
                value
            } else {
                let result = self.source * self.source; // assume this is expensive
                self.result.set(Some(result));
                result
            }
        }
    }
}

上面的代码在第一眼就能起作用.但我想知道这种做法是否有任何表面上可能不明显的潜在问题.

我对Rust的内存模型、编译器优化、机器代码级语义等不太了解,这在这种情况下可能会导致问题.

即使这种方法对当前的平台有效,我也想知道我future 是否会遇到关于未定义行为的潜在问题.

推荐答案

No. There is absolutely no way to have interior mutability in Rust without 100.

您的代码是UB和Miri marks it as such:

error: Undefined Behavior: attempting a write access using <1698> at alloc880[0x0], but that tag only grants SharedReadOnly permission for this location
  --> src/main.rs:20:18
   |
20 |         unsafe { std::ptr::write_volatile(ptr, value) }
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                  |
   |                  attempting a write access using <1698> at alloc880[0x0], but that tag only grants SharedReadOnly permission for this location
   |                  this error occurs as part of an access at alloc880[0x0..0x4]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <1698> was created by a SharedReadOnly retag at offsets [0x0..0x4]
  --> src/main.rs:18:19
   |
18 |         let ptr = &self.value as *const T;
   |                   ^^^^^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `CopyCell::<i32>::set` at src/main.rs:20:18: 20:54
note: inside `main`
  --> src/main.rs:51:5
   |
51 |     a.set(123);
   |     ^^^^^^^^^^

通过使用Volatile,您只是欺骗了优化器,使您的代码"工作",但它仍然是UB.

People want indeed an UnsafeCell that is Copy,但现在添加它是一个问题,因为人们依赖T: Copy来表示T里面没有UnsafeCell.不过,也许有一天会加进go .

Rust相关问答推荐

将此字符串转换为由空格字符分隔的空格

在rust中如何修改一个盒装函数并将其赋回?

使用元组执行条件分支的正确方法

在自身功能上实现类似移动的行为,以允许通过大小的所有者进行呼叫(&;mut;self)?

当一个箱子有自己的依赖关系时,两个人如何克服S每箱1库+n箱的限制?

如何向下转换到MyStruct并访问Arc Mutex MyStruct实现的方法?

如何正确重新排列代码以绕过铁 rust 借入判断器?

我是否可以在Ruust中修改 struct 实例上的字符串,以使其在修改后具有相同的字符串生存期?

JSON5中的变量类型(serde)

允许 rust 迹 struct 条目具有多种类型

如何在AVX2中对齐/旋转256位向量?

为什么 js_sys Promise::new 需要 FnMut?

详尽的匹配模式绑定

可选包装枚举的反序列化

`use` 和 `crate` 关键字在 Rust 项目中效果不佳

如何基于常量在Rust中跳过一个测试

Rust 中 Mutex<> 的深拷贝?

为什么我可以同时传递可变和不可变引用?

为什么 Rust 编译器在移动不可变值时执行复制?

为什么-x试图解析为文字并在声明性宏中失败?