2017-02-21 1 views
2

Wenn wir this ausführen, dann erhalten wir korrekt den Fehler "nicht zu unveränderlichen Feld a.x zuweisen".Interior Mutabilität vs Daten zu verbergen, um den Verweis auf eine veränderbare Borrow behoben

Wenn wir die zwei // Kommentare entfernen, und diese schlechte Zeile auskommentieren, dann erhalten wir den Fehler "kann Daten in einer & Referenz nicht zuweisen". Dies macht Sinn, weil &mut keine innere Veränderlichkeit bietet. Wir können eine &A frei reborrow, so dass dies keinen veränderbaren Zugang geben darf, ala &&mut ist &&.

Wenn wir beide die // Kommentare und die /* */ Kommentare entfernen, dann stellt das Ganze, die schlechte Linie ermöglicht, dass unsere invariant verletzt, dass a.x darf nie wieder etwas anderes hingewiesen werden.

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

Wie sollte man diese Invarianten behaupten, dass x darf nicht verändert werden? Wir könnten das Feld privat machen, während wir öffentliche Dereferenzierungsmethoden bereitstellen, außer das Schreiben von Konstruktoren für A in inakzeptabel.

Wir können den widerlichen Konstruktor vermeiden, indem wir ein A ein privates Mitglied eines Wrappers struct AA(A) machen, der selbst die öffentlichen Dereferenzierungsmethoden hostet. Jetzt AA benötigt einen trivialen Konstruktor, aber es braucht keine Argumente für alle Felder von A, wirkt sich nicht auf die Reihenfolge der Ausführung, etc. Dies wird schmerzhaft, wenn wir einige Eigenschaften sowohl für A als auch AA implementiert benötigen.

Noch ein anderer Ansatz wäre, die interne Veränderlichkeit zu verwenden, indem man mit Cell<A> arbeitet, mit Cell::replace darauf zugreift und es später zurücksetzt. Das klingt sehr problematisch, zeigt aber, dass mehr Lösungen existieren.

Alle saubereren Ansätze?

+0

Die andere Option ist die Verwendung einer 'Zelle' innerhalb des Arrays. Hast du das versucht? –

Antwort

1

Anstatt verwendet ein Cell<A> Sie das Array machen könnten innerhalb der ACell<u8> s enthalten:

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

Dies wird nach wie vor verhalten, wie Sie wollen, bei gleicher Leistung, aber jetzt ist das a Variable ist unveränderlich, so Sie können nicht a.x ändern. Sie müssen die Array-Referenz auch nicht änderbar machen.

Der leichte Nachteil bei Ihrem Beispiel ist, dass Sie die Array-Wiederholungssyntax nicht verwenden können, da Cell<T>Copy nicht implementiert. Dies scheint eine Unterlassung zu sein, aber es gibt eine Erklärung dafür, warum dies here ist.

+0

Interessant. :) Ich nehme an, man könnte 'x: Cell <[u8; 3]>' auch nehmen, um Array-Syntax zu geben, aber das könnte mehr Daten bewegen. Tatsächlich habe ich ein paar dieser 'x', die ein Stück von woanders zerhacken, also würde ich' transmute' brauchen, um 'Cell' zu verwenden, aber das ist schön zu wissen. Ich könnte wahrscheinlich die 'x: Cell <[u8; 3]>' Version machen, wenn es eine' split_at_mut' für 'Cell <&mut [T]>' oder so etwas gäbe. Vielen Dank! –

+0

Sie _could_ do 'let a = A {x: & unsicher {mem :: transmute ([0u8; 3])}};' - aber es ist auch nicht gerade hübsch :) –

+0

Ich denke, eine Kiste, die Transmutation sicher behandelt zwischen '& mut [T]' und '& mut [Cell ]' und zwischen '& mut [T; n] 'und' & mut [Cell ; n] 'klingt sinnvoll. :) –

1

Ein weiterer Ansatz ist

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

wo

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

Wie geschrieben zu definieren, ist dies nicht das Problem per se nicht verhindern, aber es erfordert, dass Sie den HideMut::new() Konstruktor rufen sie zu verletzen.

Nun, wenn wir HideMut im selben Modul wie A definieren und vielleicht nicht einmal exportieren, dann erreichen wir tatsächlich das gewünschte Verstecken ohne innere Veränderlichkeit.

Dieses zweite Formular erfüllt meine ursprünglichen Anforderungen nicht, da Sie den Konstruktor A { } jetzt nicht verwenden können, aber je nachdem, warum Sie keinen Konstruktor für A schreiben möchten, kann es funktionieren.

In beiden Form vermeidet dies die gesamte A zu leihen, wie eine Methode tun würde.

Verwandte Themen