Почему Rust не может принудительно вызвать изменяемую ссылку на неизменяемую ссылку в конструкторе типов? - PullRequest
3 голосов
/ 12 апреля 2020

Можно привести &mut T к &T, но это не сработает, если в конструкторе типов происходит несоответствие типов.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=648dd22adce90d86169d4f82ceeedcde

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

Для приведенного выше кода я получаю следующую ошибку:

  |
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]>>`

Можно принудительно принудительно * &mut i32 * &i32, так почему он не применяется в этой ситуации? Не могли бы вы привести несколько примеров того, как это может иметь неприятные последствия?

Ответы [ 2 ]

4 голосов
/ 12 апреля 2020

Как правило, принудительно 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 }
}

Этот тип имеет свойство, которое &Wrapper<&T> автоматически разыменовывается до &T, а &mut Wrapper<&mut T> автоматически разыменовывается до &mut T. Кроме того, Wrapper<T> можно скопировать, если T.

Предположим, что существует функция, которая может принять &Wrapper<&mut T> и привести его к &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
    }
}

Используя эту функцию, можно получить изменяемую и неизменную ссылку на одно и то же значение одновременно:

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"
}

Пример полной игровой площадки

Это не допускается в Rust, приводит к неопределенному поведению и означает, что функция downgrade_wrapper_ref в этом случае не работает. Могут быть и другие конкретные c случаи, когда вы, как программист, можете гарантировать, что этого не произойдет, но вам все равно потребуется реализовать его специально для этих случаев, используя код unsafe, чтобы убедиться, что вы берете ответственность за предоставление этих гарантий.

2 голосов
/ 12 апреля 2020

Рассмотрите эту проверку для пустой строки, которая опирается на 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(""))
}

(площадка)

Этот код не компилируется, поскольку &mut String не приводит к &String. Однако, если бы это было так, то вновь созданный поток изменил бы content после вызова format!, но до сравнения в функции is_empty, что сделало бы недействительным предположение о неизменности содержимого контейнера, что требуется для пустого чека.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...