2015-05-16 17 views
13

Da std::cell documentation, vedo che Cell è "compatibile solo con i tipi che implementano Copy". Ciò significa che è necessario utilizzare RefCell per i tipi non Copy.Quando posso usare Cell o RefCell, quale dovrei scegliere?

Quando I do ha un tipo Copy, c'è un vantaggio nell'usare un tipo di cella rispetto a un altro? Presumo che la risposta sia "sì", perché altrimenti non esisterebbero entrambi i tipi! Quali sono i vantaggi e gli svantaggi dell'utilizzo di un tipo rispetto all'altro?

Ecco un stupido esempio, made-up che utilizza sia Cell e RefCell per raggiungere lo stesso obiettivo:

use std::cell::{Cell,RefCell}; 

struct ThingWithCell { 
    counter: Cell<u8>, 
} 

impl ThingWithCell { 
    fn new() -> ThingWithCell { 
     ThingWithCell { counter: Cell::new(0) } 
    } 

    fn increment(&self) { 
     self.counter.set(self.counter.get() + 1); 
    } 

    fn count(&self) -> u8 { self.counter.get() } 
} 

struct ThingWithRefCell { 
    counter: RefCell<u8>, 
} 

impl ThingWithRefCell { 
    fn new() -> ThingWithRefCell { 
     ThingWithRefCell { counter: RefCell::new(0) } 
    } 

    fn increment(&self) { 
     let mut counter = self.counter.borrow_mut(); 
     *counter = *counter + 1; 
    } 

    fn count(&self) -> u8 { *self.counter.borrow_mut() } 
} 


fn main() { 
    let cell = ThingWithCell::new(); 
    cell.increment(); 
    println!("{}", cell.count()); 

    let cell = ThingWithRefCell::new(); 
    cell.increment(); 
    println!("{}", cell.count()); 
} 

risposta

13

penso che è importante prendere in considerazione le altre differenze semantiche tra Cell e RefCell:

  • Cell fornisce valori, RefCell con riferimenti
  • Cell mai panico, RefCell possono prendere dal panico

Immaginiamo una situazione in cui queste differenze siano importanti:

let cell = Cell::new(foo); 
{ 
    let mut value = cell.get(); 
    // do some heavy processing on value 
    cell.set(value); 
} 

In questo caso, se immaginiamo un po 'il flusso di lavoro complesso con un sacco di callback e che cell fa parte di uno stato globale, è possibile che il contenuto di cell sono modificati come effetto collaterale del "trattamento pesante "e queste potenziali modifiche andranno perse quando value viene riscritto in cell.

D'altro canto, un codice simile usando RefCell:

let cell = RefCell::new(foo); 
{ 
    let mut_ref = cell.borrow_mut().unwrap(); 
    // do some heavy processing on mut_ref 
} 

In questo caso, qualsiasi modifica di cell come effetto collaterale della "lavorazione pesante" è proibito, e si tradurrebbe in un panico . È in tal modo è certi che il valore di cell non cambierà senza utilizzare mut_ref

avrei decidere quale usare a seconda della semantica del valore tiene, piuttosto che semplicemente il Copy trait.Se entrambi sono accettabili, allora Cell è più leggero e più sicuro dell'altro, e quindi sarebbe preferibile.

+1

È possibile ottenere la semantica del valore con 'RefCell': clonare lo stato iniziale, lavorarci sopra e riscrivere lo stato modificato nella cella alla fine. –

+0

@MatthieuM. ma non puoi ottenere la semantica di riferimento con 'Cell', corretto? – Shepmaster

+1

@Shepmaster: Beh, puoi avere riferimenti a 'Cell'. Se vuoi un riferimento al valore puoi usare il suo componente 'UnsafeCell', e' UnsafeCell' può darti un puntatore al valore di riferimento ... ma non è sicuro (come per il nome). Quindi, no, 'Cell' non è pensato per manipolare i riferimenti; non sarebbe al sicuro altrimenti, come si rompe dai controlli di prestito. –

9

Si dovrebbe usare Cell, se potete.

Cell non utilizza alcun controllo di runtime. Tutto ciò che fa è un incapsulamento che non consente l'aliasing e dice al compilatore che si tratta di uno slot internamente modificabile. Nella maggior parte dei casi dovrebbe compilare il codice che è esattamente lo stesso come se fosse il tipo senza cell wrapping.

In confronto, RefCell utilizza un semplice contatore di utilizzo per controllare il prestito rispetto a mutuo prestito in fase di esecuzione e tale controllo può portare a un panico in fase di esecuzione se si viola ad esempio l'esclusività del prestito mutabile. Il possibile panico può essere un impedimento all'ottimizzazione.

Ci sono alcune altre differenze, un Cell non ti permetterà mai di ottenere un puntatore al valore memorizzato stesso, quindi se ne hai bisogno temporaneamente uno RefCell è l'unica scelta.

+0

Dove posso ottenere ulteriori informazioni su come non consente l'aliasing? Può usare questo per dichiararlo come 'noalias'? https://github.com/rust-lang/rust/issues/31681 sembra suggerire una funzione correlata dai puntatori di '& mut'. Ma non è possibile che un blocco "non sicuro" sia potenzialmente alias? Se scrivo un blocco 'non sicuro ', spetta a me assicurarmi di non creare un riferimento aliasing o un dereferenziamento qualsiasi che alias un' Cella' da qualche parte? –

+2

Come disabilita l'aliasing: non ti consente di ottenere un riferimento (quindi nessun puntatore) al valore ** nella ** cella. L'ultima domanda è facile da rispondere: Sì, dipende da te, i blocchi non sicuri sono un esplicito "Fiducia del programmatore". Come si suol dire, i blocchi "non sicuri" non sono usati per spezzare gli invarianti di Rust, ma per mantenerli manualmente. – bluss

+0

Sono solo io o quel particolare aspetto dell'onere di blocchi "non sicuri" davvero sottile? –