2015-06-05 21 views
8

Ho difficoltà a comprendere le regole sui tratti nei tipi di dati algebrici. Ecco un esempio semplificato:Tratti nei tipi di dati algebrici

use std::rc::Rc; 
use std::cell::RefCell; 

trait Quack { 
    fn quack(&self); 
} 

struct Duck; 

impl Quack for Duck { 
    fn quack(&self) { println!("Quack!"); } 
} 

fn main() { 
    let mut pond: Vec<Box<Quack>> = Vec::new(); 
    let duck: Box<Duck> = Box::new(Duck); 
    pond.push(duck); // This is valid. 

    let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
    let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
    lake.push(mallard); // This is a type mismatch. 
} 

È possibile che non riesce a compilare, ottenendo il seguente messaggio di errore:

expected `alloc::rc::Rc<core::cell::RefCell<Box<Quack>>>`, 
    found `alloc::rc::Rc<core::cell::RefCell<Box<Duck>>>` 
(expected trait Quack, 
    found struct `Duck`) [E0308] 
src/main.rs:19  lake.push(mallard); 

Perché è che pond.push(duck) è valido, ma non è lake.push(mallard)? In entrambi i casi, è stato fornito un Duck in cui era previsto un Quack. Nel primo caso, il compilatore è felice, ma nel secondo caso non lo è.

Il motivo di questa differenza è relativo a CoerceUnsized?

+0

Nota: 'RefCell' non è necessario qui, posso riprodurre il problema con' Rc > '. –

+0

Matthieu, hai ragione. 'RefCell' non è necessario per far si che l'errore si verifichi. Sulla base della risposta di Vladimir in basso, posso capire perché lo stesso errore si verifica con o senza 'RefCell'. – rlkw1024

risposta

6

Questo è un comportamento corretto, anche se è un po 'sfortunato.

Nel primo caso abbiamo questa:

noti che push(), quando ha invitato Vec<Box<Quack>>, accetta Box<Quack>, e si sta passando Box<Duck>. Questo è OK - rustc è in grado di capire che si desidera convertire un valore in scatola a un oggetto tratto, come qui:

let duck: Box<Duck> = Box::new(Duck); 
let quack: Box<Quack> = duck; // automatic coercion to a trait object 

Nel secondo caso abbiamo questo:

let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
lake.push(mallard); 

Qui push() accetta Rc<RefCell<Box<Quack>>> mentre si fornisce Rc<RefCell<Box<Duck>>>:

let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
let quack: Rc<RefCell<Box<Quack>>> = mallard; 

e ora c'è un problema. Box<T> è un tipo compatibile con DST, quindi può essere utilizzato come contenitore per un oggetto tratto. La stessa cosa sarà presto vera per Rc e altri puntatori intelligenti quando è implementato lo this RFC. Tuttavia, in questo caso non vi è alcuna coercizione da un tipo concreto a un oggetto tratto perché Box<Duck> si trova all'interno di livelli aggiuntivi di tipi (Rc<RefCell<..>>).

Ricordare, l'oggetto tratto è un puntatore grasso, quindi Box<Duck> è diverso dalla dimensione Box<Quack>. Di conseguenza, in linea di principio, non sono direttamente compatibili: non puoi semplicemente prendere i byte di Box<Duck> e scriverli dove è atteso Box<Quack>. Rust esegue una conversione speciale, ovvero ottiene un puntatore alla tabella virtuale per Duck, costruisce un puntatore grassetto e lo scrive nella variabile di tipo Box<Quack>.

Quando si dispone di Rc<RefCell<Box<Duck>>>, tuttavia, rustc avrebbe bisogno di sapere come costruire e destrutturare sia RefCell e Rc al fine di applicare la stessa conversione puntatore grasso per i suoi interni. Naturalmente, poiché questi sono tipi di libreria, non può sapere come farlo. Questo vale anche per qualsiasi altro tipo di wrapper, ad es. Arc o Mutex o anche Vec. Non ti aspetti che sarebbe possibile utilizzare Vec<Box<Duck>> come Vec<Box<Quack>>, giusto?

Inoltre, nell'esempio con Rc gli Rcs creati da Box<Duck> e Box<Quack> non sarebbero stati collegati: avrebbero avuto contatori di riferimento diversi.

Ovvero, una conversione da un tipo concreto a un oggetto tratto può avvenire solo se si ha accesso diretto a un puntatore intelligente che supporta l'ora legale, non quando è nascosto all'interno di un'altra struttura.

Detto questo, vedo come sia possibile impostare per alcuni tipi di selezione. Ad esempio, potremmo introdurre alcuni tipi di tratti di Construct/Unwrap che sono noti al compilatore e che potrebbe utilizzare per "raggiungere" all'interno di una pila di wrapper ed eseguire la conversione dell'oggetto trait al loro interno. Tuttavia, nessuno ha progettato questa cosa e ne ha fornito ancora una RFC - probabilmente perché non è una caratteristica ampiamente necessaria.

+0

Grazie! Come implementeresti questo schema? Il pattern intendo è questo: hai un sacco di oggetti mutabili. Per efficienza, è possibile accedervi tramite diverse strutture di ricerca, ad es. un hash, un vassoio binario e un vettore. Alcune di queste strutture di ricerca necessitano di oggetti di diverso tipo che implementano tutti un tratto. Potrei avvolgerli in Enums. Oppure potrei creare strutture wrapper che possiedono Box. – rlkw1024

+0

In base a questo RFC (https://github.com/rust-lang/rfcs/blob/master/text/0982-dst-coercion.md) il tipo di wrapper di destinazione dovrebbe implementare CoerceUnsized per rendere possibile l'auto-coerenza del tratto . Tuttavia RefCell al momento manca questa implementazione, ma è comunque coassiale per le entità basate su stack (https://stackoverflow.com/questions/30861295/how-to-i-pass-rcrefcellboxmystruct-to-a-function-accepting-rcrefcellbox). Box implementa CoerceUnsized ed è coelabile da solo, ma non in RefCell, che è assolutamente strano. – snuk182

+0

Ho provato ad implementare il proprio RefCell che implementa CoerceUnsized, senza successo (https://play.rust-lang.org/?gist=27e5b540bfceaa9db79abea5f6526d48&version=nightly&backtrace=2), ed è ancora più incerto perché è così, causa il RefCell è solo un controllore del prestito in runtime per UnsafeCell, che è solo un wrapper come QuackWrap dell'esempio qui sotto. – snuk182

1

Vladimir's answer ha spiegato cosa sta facendo il compilatore . Sulla base di tali informazioni, ho sviluppato una soluzione: Creazione di una struttura wrapper attorno a Box<Quack>.

Il wrapper si chiama QuackWrap. Ha una dimensione fissa e può essere utilizzato come qualsiasi altra struttura (penso). Il numero Box all'interno di QuackWrap mi consente di creare un intorno a qualsiasi tratto che implementa Quack. Così, posso avere un Vec<Rc<RefCell<QuackWrap>>> dove i valori interiori sono una miscela di Duck s, Goose s, ecc

use std::rc::Rc; 
use std::cell::RefCell; 

trait Quack { 
    fn quack(&self); 
} 

struct Duck; 

impl Quack for Duck { 
    fn quack(&self) { println!("Quack!"); } 
} 

struct QuackWrap(Box<Quack>); 

impl QuackWrap { 
    pub fn new<T: Quack + 'static>(value: T) -> QuackWrap { 
     QuackWrap(Box::new(value)) 
    } 
} 

fn main() { 
    let mut pond: Vec<Box<Quack>> = Vec::new(); 
    let duck: Box<Duck> = Box::new(Duck); 
    pond.push(duck); // This is valid. 

    // This would be a type error: 
    //let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
    //let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
    //lake.push(mallard); // This is a type mismatch. 

    // Instead, we can do this: 
    let mut lake: Vec<Rc<RefCell<QuackWrap>>> = Vec::new(); 
    let mallard: Rc<RefCell<QuackWrap>> = Rc::new(RefCell::new(QuackWrap::new(Duck))); 
    lake.push(mallard); // This is valid. 
} 

Come ulteriore comodità, io probabilmente voglio implementare Deref e DefrefMut su QuackWrap. Ma non è necessario per l'esempio sopra.

Problemi correlati