为什么 Rust 不能在类型构造函数中将可变引用强制为不可变引用?

pku*_*bik 4 immutability coercion rust type-constructor

可以强制&mut T进入,&T但如果类型不匹配发生在类型构造函数中,则它不起作用。

操场

use ndarray::*; // 0.13.0

fn print(a: &ArrayView1<i32>) {
    println!("{:?}", a);
}

pub fn test() {
    let mut x = array![1i32, 2, 3];
    print(&x.view_mut());
}
Run Code Online (Sandbox Code Playgroud)

对于上面的代码,我收到以下错误:

  |
9 |     print(&x.view_mut());
  |           ^^^^^^^^^^^^^ types differ in mutability
  |
  = note: expected reference `&ndarray::ArrayBase<ndarray::ViewRepr<&i32>, ndarray::dimension::dim::Dim<[usize; 1]>>`
             found reference `&ndarray::ArrayBase<ndarray::ViewRepr<&mut i32>, ndarray::dimension::dim::Dim<[usize; 1]>>`
Run Code Online (Sandbox Code Playgroud)

强制&mut i32执行是安全的,&i32那么为什么在这种情况下不适用呢?你能否提供一些例子说明它怎么可能适得其反?

Frx*_*rem 6

一般来说,强制Type<&mut T>进入Type<&T>.

例如,考虑这个包装器类型,它是在没有任何不安全代码的情况下实现的,因此是合理的:

#[derive(Copy, Clone)]
struct Wrapper<T>(T);

impl<T: Deref> Deref for Wrapper<T> {
    type Target = T::Target;
    fn deref(&self) -> &T::Target { &self.0 }
}

impl<T: DerefMut> DerefMut for Wrapper<T> {
    fn deref_mut(&mut self) -> &mut T::Target { &mut self.0 }
}
Run Code Online (Sandbox Code Playgroud)

此类型具有&Wrapper<&T>自动取消引用&T&mut Wrapper<&mut T>自动取消引用的属性&mut T。此外,Wrapper<T>如果T是,则是可复制的。

假设存在一个可以将 a&Wrapper<&mut T>强制转换为 a的函数&Wrapper<&T>

fn downgrade_wrapper_ref<'a, 'b, T: ?Sized>(w: &'a Wrapper<&'b mut T>) -> &'a Wrapper<&'b T> {
    unsafe {
        // the internals of this function is not important
    }
}
Run Code Online (Sandbox Code Playgroud)

通过使用这个函数,可以同时获得对同一个值的可变和不可变引用:

fn main() {
    let mut value: i32 = 0;

    let mut x: Wrapper<&mut i32> = Wrapper(&mut value);

    let x_ref: &Wrapper<&mut i32> = &x;
    let y_ref: &Wrapper<&i32> = downgrade_wrapper_ref(x_ref);
    let y: Wrapper<&i32> = *y_ref;

    let a: &mut i32 = &mut *x;
    let b: &i32 = &*y;

    // these two lines will print the same addresses
    // meaning the references point to the same value!
    println!("a = {:p}", a as &mut i32); // "a = 0x7ffe56ca6ba4"
    println!("b = {:p}", b as &i32);     // "b = 0x7ffe56ca6ba4"
}
Run Code Online (Sandbox Code Playgroud)

完整的操场示例

这在 Rust 中是不允许的,会导致未定义的行为并意味着downgrade_wrapper_ref在这种情况下函数是不健全的。可能还有其他特定情况,作为程序员,您可以保证不会发生这种情况,但它仍然需要您使用unsafe代码专门针对这些情况实现它,以确保您承担做出这些保证的责任。


msr*_*rd0 3

考虑对空字符串的检查,该检查依赖于content在函数运行时保持不变is_empty(仅出于说明目的,不要在生产代码中使用它):

struct Container<T> {
    content: T
}

impl<T> Container<T> {
    fn new(content: T) -> Self
    {
        Self { content }
    }
}

impl<'a> Container<&'a String> {
    fn is_empty(&self, s: &str) -> bool
    {
        let str = format!("{}{}", self.content, s);
        &str == s
    }
}

fn main() {
    let mut foo : String = "foo".to_owned();
    let container : Container<&mut String> = Container::new(&mut foo);

    std::thread::spawn(|| {
        container.content.replace_range(1..2, "");
    });

    println!("an empty str is actually empty: {}", container.is_empty(""))
}
Run Code Online (Sandbox Code Playgroud)

(操场)

此代码无法编译,因为&mut String不会强制转换为&String. 但是,如果确实如此,新创建的线程可能会在content调用之后format!但在函数中的相等比较之前更改is_empty,从而使容器内容不可变的假设无效,而这是空检查所需的。