2017-04-04 1 views
10
fn works<'a>(foo: &Option<&'a mut String>, s: &'a mut String) {} 
fn error<'a>(foo: &RefCell<Option<&'a mut String>>, s: &'a mut String) {} 

let mut s = "hi".to_string(); 

let foo = None; 
works(&foo, &mut s); 

// with this, it errors 
// let bar = RefCell::new(None); 
// error(&bar, &mut s); 

s.len(); 

Wenn ich in den beiden Leitungen mit dem Kommentar setzen, der folgende Fehler auftritt:Liegt dieser Fehler am speziellen Wissen des Compilers über RefCell?

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable 
    --> <anon>:16:5 
    | 
14 |  error(&bar, &mut s); 
    |      - mutable borrow occurs here 
15 |  
16 |  s.len(); 
    | ^immutable borrow occurs here 
17 | } 
    | - mutable borrow ends here 

Die Unterschriften von works() und errors() sehen ziemlich ähnlich. Aber anscheinend weiß der Compiler, dass man mit einem RefCell darauf schummeln kann, weil sich der Borrow-Checker anders verhält.

Ich kann sogar die RefCell in einem anderen Typ von mir "verstecken", aber der Compiler immer noch das Richtige (Fehler, falls eine RefCell könnte verwendet werden). Woher weiß der Compiler das ganze Zeug und wie funktioniert es? Markiert der Compiler Typen als "Interior Mutability Container" oder ähnliches?

Antwort

8

RefCell<T> enthält eine UnsafeCell<T>, die eine spezielle lang item ist. Es ist UnsafeCell, die den Fehler verursacht. Sie könnten mit überprüfen:

fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {} 

... 

let bar = UnsafeCell::new(None); 
error(&bar, &mut s); 

Aber der Fehler ist nicht durch ein UnsafeCell führt Innere Veränderlichkeit Compiler erkennen, sondern dass ein UnsafeCellinvariant in T. In der Tat ist, könnten wir den Fehler mit PhantomData reproduzieren:

struct Contravariant<T>(PhantomData<fn(T)>); 

fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {} 

... 

let bar = Contravariant(PhantomData); 
error(bar, &mut s); 

oder auch nur irgendetwas, das kontra oder invariant in der Lebensdauer ist 'a:

fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {} 

let bar = None; 
error(bar, &mut s); 

Der Grund, warum eine RefCell nicht versteckt werden kann, ist, dass die Varianz durch die Felder der Struktur abgeleitet wird. Sobald Sie irgendwo RefCell<T> verwendet haben, egal wie tief, der Compiler wird herausfinden, T ist invariant.


Jetzt sehen wir uns an, wie der Compiler den Fehler E0502 ermittelt. Erstens, es ist wichtig, sich daran zu erinnern, dass der Compiler zwei bestimmte Lebensdauern hier wählen hat: die Lebensdauer in der Art des Ausdrucks &mut s ('a) und die Lebensdauer in der Art der bar (nennen wir es 'x). Beide sind beschränkt: die ehemalige Lebensdauer 'a muss kürzer sein als der Umfang der s, sonst hätten wir mit einer Referenz am Ende lebt länger als die ursprüngliche Zeichenfolge. 'x muss größer sein als der Umfang der bar, sonst könnten wir einen baumelnden Zeiger durch bar zugreifen (wenn ein Typ ein Leben lang Parameter der Compiler hat nimmt die Art, die einen Wert mit diesem Leben zugreifen kann).

Mit diesen beiden grundlegenden Einschränkung, geht der Compiler durch die folgenden Schritte:

  1. Der Typ barContravariant<&'x i32> ist.
  2. Die Funktion error akzeptiert jeden Subtyp von Contravariant<&'a i32>, wobei 'a die Lebensdauer dieses Ausdrucks &mut s ist.
  3. bar Somit sollte das heißt kontra über T, ein Subtyp von Contravariant<&'a i32>
  4. Contravariant<T> ist seinwenn U <: T, dann Contravariant<T> <: Contravariant<U>.
  5. So kann die Subtyping-Beziehung erfüllt werden, wenn &'x i32 ein Supertyp von &'a i32 ist.
  6. 'x Somit sollte kürzer als 'a, d.h. 'a sein sollte 'x überleben.

ähnliche Weise wird für einen invarianten-Typen, die abgeleitete Beziehung 'a == 'x und für convariant, 'x'a überlebt.

Nun, hier ist das Problem, dass die Lebensdauer im Typ von bar Leben bis zum Ende des Gültigkeitsbereiches (gemäß Einschränkung oben erwähnt):

let bar = Contravariant(PhantomData); // <--- 'x starts here -----+ 
    error(bar,        //       | 
      &mut s);       // <- 'a starts here ---+ | 
    s.len();        //      | | 
              // <--- 'x ends here¹ --+---+ 
              //      | 
              // <--- 'a ends here² --+ 
} 

// ¹ when `bar` goes out of scope 
// ² 'a has to outlive 'x 
sowohl

In kontra und invariant Fällen 'a überlebt (oder gleich zu) 'x bedeutet, dass die Anweisung s.len() in den Bereich aufgenommen werden muss, was zu einem Fehler beim Ausleihen führt.

Nur im covariant Fall, dass wir den Bereich der 'a kürzer als 'x machen könnten, so dass das temporäre Objekt fällt gelassen wird &mut s vor s.len() genannt wird (im Sinne von: bei s.len() wird s nicht ausgeliehen mehr berücksichtigt):

let bar = Covariant(PhantomData);  // <--- 'x starts here -----+ 
              //       | 
    error(bar,        //       | 
      &mut s);       // <- 'a starts here --+ | 
              //      | | 
              // <- 'a ends here ----+ | 
    s.len();        //       | 
}           // <--- 'x ends here -------+ 
+0

Oh mein! Was für eine tolle Antwort! Ich hatte schon befürchtet, dass die Antwort einfach "RefCell" ist etwas Besonderes, aber das ist eine erstaunliche Erkenntnis. Danke ♥ Eine Frage: Im 'foo: Option ' Fall können wir die Speichersicherheit tatsächlich unterbrechen? Oder ist es im Grunde ein falsches Positiv, weil der Compiler intern denkt? –

+0

@LukasKalbertodt Wenn es zusätzliche Erklärung, keine Fragen, fühlen Sie sich frei. (Wenn es zusätzliche verwandte Fragen sind, dass das Kommentarfeld zu klein ist, bearbeiten Sie stattdessen die Frage). – kennytm

+0

@LukasKalbertodt Ich glaube, der 'Option ' Fall ist ein falsches positives, das mit nicht-lexikalischer Lebenszeit gelöst werden kann. Aber ich habe nicht im Detail geprüft, vielleicht hätte es eine schlechte Interaktion mit Threads. – kennytm

Verwandte Themen