Внутренняя изменчивость против данных, скрывающих, чтобы держать фиксированным референтом изменяемого заимствования
Если мы запустим это, то мы правильно получим ошибку "нельзя присвоить неизменному полю 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
как метод будет делать.