强制转换为“*mut”会否决非“mut”的引用

V0l*_*dek 5 rust borrow-checker

我将结构体的字段转换为指针*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]);
}
Run Code Online (Sandbox Code Playgroud)
1
0
Run Code Online (Sandbox Code Playgroud)

游乐场链接

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

这种行为是否符合预期?难道不应该强制转换*mut要求操作数位于mut第一位吗?是否有令人信服的理由说明为什么不需要?

orl*_*rlp 12

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

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

所以现在我们需要重写why

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

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

pub fn get_bytes(&self) -> &[u8] {
    &self.bytes
}
Run Code Online (Sandbox Code Playgroud)

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

pub fn get_bytes_mut(&mut self) -> &mut [u8] {
    &mut self.bytes
}
Run Code Online (Sandbox Code Playgroud)

然而,你仍然没有完成,如下as_ptr所示:

调用者还必须确保永远不会使用此指针或从其派生的任何指针写入指针(非传递性)指向的内存(UnsafeCell 内部除外)。如果需要改变切片的内容,请使用 as_mut_ptr。

as_mut_ptr指出:

调用者必须确保切片比该函数返回的指针寿命更长,否则它将最终指向垃圾。

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

pub fn get_bytes(&self) -> &[u8] {
    &self.bytes
}
Run Code Online (Sandbox Code Playgroud)

  • @V0ldek因为构造这样的指针并不是未定义的行为,只是错误地*使用*它们。这与引用形成鲜明对比,在引用中,甚至构造它们都是未定义的行为。 (4认同)