转换为 `*mut` 否决引用不是 `mut`

Casting to `*mut` overrules the reference not being `mut`

我将结构的字段转换为 *mut 指针,因此我将该结构的引用声明为可变的。但是后来我开始收到警告,指出不需要 mut 修饰符。这对我来说似乎很奇怪,我显然正在改变某些东西,但将其声明为 mut 是不必要的吗?这让我想到了这个最小的例子:

struct NonMut {
    bytes: [u8; 5]
}

impl NonMut {
    pub fn get_bytes(&self) -> &[u8] {
        &self.bytes
    }
}

fn potentially_mutate(ptr: *mut u8) {
    unsafe { *ptr = 0 }
}

fn why(x: &NonMut) {
    let ptr = x.get_bytes().as_ptr() as *mut u8;
    
    potentially_mutate(ptr);

}

fn main() {
    let x = NonMut { bytes: [1, 2, 3, 4, 5] };
    println!("{}", x.get_bytes()[0]);

    why(&x);
    
    println!("{}", x.get_bytes()[0]);
}
1
0

Playground link

显然取消引用指针需要不安全的代码,但从某人刚刚编写自己的应用程序并决定调用 why 的角度来看,可能在外部包中定义,这种行为似乎完全出乎意料。你在某处传递了一个明显不可变的引用,借用检查器将你的代码标记为正确,但在幕后有一个为该结构创建的可变引用。这可能会导致在 why 的用户不知道的情况下为 NonMut 创建多个活动可变引用。

这种行为是否符合预期?转换为 *mut 不应该首先要求操作数是 mut 吗?是否有令人信服的理由说明为什么不需要它?

问题 #1:potentially_mutate 被错误地标记为安全函数,同时它可能导致未定义的行为(例如,如果我传入无效指针怎么办?)。所以我们需要将其标记为 unsafe 并记录我们的安全使用假设:

/// Safety: must pass a valid mutable pointer.
unsafe fn potentially_mutate(ptr: *mut u8) {
    unsafe { *ptr = 0 }
}

所以现在我们需要重写why:

fn why(x: &NonMut) {
    let ptr = x.get_bytes().as_ptr() as *mut u8;
    
    unsafe {
        potentially_mutate(ptr);
    }
}

现在我们暴露了问题 #2:我们违反了 potentially_mutate 所做的假设。我们将它传递给一个指向不可变数据的指针,声称它是可变的。这是未定义的行为!如果我们有 x: &mut NonMut 也不会有任何区别,这就是为什么如果您尝试这样做会收到警告。重要的是:

pub fn get_bytes(&self) -> &[u8] {
    &self.bytes
}

这里我们在调用方法时构造一个字节片的不可变引用,无论您的 x: &NonMut 是否可变。如果我们想改变我们还需要制作的字节:

pub fn get_bytes_mut(&mut self) -> &mut [u8] {
    &mut self.bytes
}

然而,您仍未完成,正如 as_ptr 所述:

The caller must also ensure that the memory the pointer (non-transitively) points to is never written to (except inside an UnsafeCell) using this pointer or any pointer derived from it. If you need to mutate the contents of the slice, use as_mut_ptr.

并且 as_mut_ptr 指出:

The caller must ensure that the slice outlives the pointer this function returns, or else it will end up pointing to garbage.

因此,为了让您的示例正确且安全:

struct Mut {
    bytes: [u8; 5]
}

impl Mut {
    pub fn get_bytes(&self) -> &[u8] {
        &self.bytes
    }
    
    pub fn get_bytes_mut(&mut self) -> &mut [u8] {
        &mut self.bytes
    }
}

unsafe fn potentially_mutate(ptr: *mut u8) {
    *ptr = 0
}

fn whynot(x: &mut Mut) {
    unsafe {
        let slice = x.get_bytes_mut(); // Keep slice alive.
        let ptr = slice.as_mut_ptr();
        potentially_mutate(ptr);
    }
}

fn main() {
    let mut x = Mut { bytes: [1, 2, 3, 4, 5] };
    println!("{}", x.get_bytes()[0]);
    whynot(&mut x);
    println!("{}", x.get_bytes()[0]);
}

您没有直接从 &u8 转换为 *mut u8

fn allowed_but_ub(x: &[u8]) {
    let x_ptr   = x.as_ptr();    // &[u8]     -> *const u8
    let _ptr = x_ptr as *mut u8; // *const u8 -> *mut u8 
}

Playground

允许从 *const T 转换为 *mut T

如果您想对可变性进行抽象并进行一些运行时检查以告诉您您的指针是来自可变源还是不可变源,这可能会很有用。

但是,不允许将不可变引用直接转换为可变指针,因此以下示例无法按预期编译:

fn not_allowed(x: &[u8]) {
    let x_ref  = &x[0];          // &[u8] -> &u8
    let _ptr = x_ref as *mut u8; // &u8   -> *mut u8
}

Playground