2017-02-21 55 views
2

Если мы запустим this, тогда мы правильно получим ошибку «не может назначить неизменяемое поле a.x».Внутренняя изменчивость по сравнению с данными, скрывающимися для фиксации референта изменяемого заимствования

Если мы удалим два комментария // и прокомментируем эту плохую строку, мы получим сообщение об ошибке «не может назначить данные в ссылке &». Это имеет смысл, потому что &mut не обеспечивает внутреннюю изменчивость. Мы можем reborrow a &A свободно, поэтому это не должно давать mutable доступ, ala &&mut is &&.

Если удалить оба // комментариев и /* */ комментариев, то все это собирает, позволяя плохую линию, нарушающий инвариант, 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, не влияет на порядок выполнения и т. Д. Это становится болезненным, если нам нужны некоторые черты, реализованные как для, так и для AA.

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

Любые очистители подходят?

+0

Другой вариант - использовать «Ячейку» внутри массива. Вы попробовали это? –

ответ

1

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

+0

Интересно. :) Я полагаю, что можно взять 'x: Cell <[u8; 3]>' тоже, чтобы дать синтаксис массива, но это может привести к перемещению большего количества данных. На самом деле, у меня есть куча этих «х», которые отрубают фрагмент из другого места, поэтому мне нужно «трансмутировать», чтобы использовать «Ячейку», но это приятно знать. Вероятно, я мог бы использовать версию 'x: Cell <[u8; 3]>', если для 'Cell <&mut [T]>'' или что-то было 'split_at_mut'. Благодаря! –

+0

Вы _could_ do 'let a = A {x: & unsafe {mem :: transformute ([0u8; 3])}};' - но это тоже не совсем так :) –

+0

Я действительно думаю, что ящик, который безопасно обрабатывает трансмутацию между '& mut [T] и' & mut [Cell ] 'и между' & mut [T; n] 'и' & mut [Cell ; n] 'звучит полезно. :) –

1

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

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 в качестве метода.