Внутренняя изменчивость против данных, скрывающих, чтобы держать фиксированным референтом изменяемого заимствования

Если мы запустим это, то мы правильно получим ошибку "нельзя присвоить неизменному полю a.x ".

Если мы удалим два // комментарии и закомментируйте эту плохую строку, тогда мы получим ошибку "невозможно присвоить данные в & ссылка ". Это имеет смысл, потому что &mut не обеспечивает изменчивость интерьера. Мы можем забрать &A свободно, так что это не должно давать изменяемый доступ, аля &&mut является &&,

Если мы удалим оба // комментарии и /* */ комментарии, затем все это компилируется, допуская плохую строку, которая нарушает наш инвариант, что a.x никогда не должен указывать на что-либо еще.

pub struct A<'a> {
    pub x: &'a mut [u8; 3],
}

fn main() {
    let y = &mut [7u8; 3];
    let /*mut*/ a = A { x: &mut [0u8; 3] };
    a.x[0] = 3;
    a.x = y;  //// This must be prevented!
    {
        // let b = &/*mut*/ a;
        // b.x[1] = 2;
    }
    println!("{:?}", a.x);
}

Как сохранить этот инвариант x не должны быть изменены? Мы могли бы сделать поле приватным, предоставляя общедоступные методы разыменования, кроме написания конструкторов для A в недопустимом.

Мы можем избежать неприятного конструктора, сделав A частный член обертки struct AA(A) который сам по себе содержит публичные методы разыменования. Сейчас AA нужен тривиальный конструктор, но он не нуждается в аргументах для всех полей A, не влияет на порядок выполнения и т. д. Это становится болезненным, если нам нужны некоторые черты, реализованные для обоих A а также AA хоть.

Тем не менее, другой подход будет использовать внутреннюю изменчивость, работая с Cell<A>, обращаясь к нему с Cell::replace и отложил его позже. Это звучит очень проблематично, но показывает, что существует больше решений.

Любой уборщик подходит?

2 ответа

Решение

Вместо того, чтобы использовать Cell<A> Вы могли бы сделать массив внутри A содержать Cell<u8>s:

use std::cell::Cell;

pub struct A<'a> {
    x: &'a [Cell<u8>; 3],
}

fn main() {
    // let y = &mut [7u8; 3];
    let a = A { x: &[Cell::new(0u8), Cell::new(0u8), Cell::new(0u8)] };
    a.x[0].set(3);
    // a.x = y;
    {
        let b = &a;
        b.x[1].set(2);
    }
    println!("{:?}", a.x);
}

Это все равно будет вести себя так, как вы хотите, с той же производительностью, но теперь a переменная неизменна, поэтому вы не можете изменить a.x, Вам также не нужно делать изменяемой ссылку на массив.

Небольшой недостаток вашего примера в том, что вы не можете использовать синтаксис повторения массива, так как Cell<T> не реализует Copy, Это кажется упущением, но есть некоторое объяснение, почему это здесь.

Другой подход заключается в определении

pub struct A<'a> { pub x: HideMut<'a,[u8; 3]> }

где

use std::ops::{Deref,DerefMut};

struct HideMut<'a,T>(&'a mut T) where T: ?Sized + 'a;

impl<'a,T> HideMut<'a,T> where T: ?Sized {
    pub fn new(m: &'a mut T) -> HideMut<'a,T> { HideMut(m) }
}

impl<'a,T> Deref for HideMut<'a,T> where T: ?Sized {
    type Target = T;
    fn deref(&self) -> &T { self.0 }
}

impl<'a,T> DerefMut for HideMut<'a,T> where T: ?Sized {
    fn deref_mut(&mut self) -> &mut T { self.0 }
}

Как написано, это не предотвращает проблему как таковую, но требует, чтобы вы вызвали HideMut::new() конструктор, чтобы нарушить это.

Теперь, если мы определим HideMut в том же модуле, что и Aи, возможно, даже не экспортируем его, тогда мы действительно достигаем желаемого сокрытия без какой-либо внутренней изменчивости.

Эта вторая форма не соответствует моим первоначальным требованиям, потому что теперь вы не можете использовать конструктор A { }, но в зависимости от ваших причин не желать писать конструктор для A это может сработать.

В любом случае это позволяет избежать заимствования всего A как метод будет делать.

Другие вопросы по тегам