2015-06-05 13 views
8

Mam problemy ze zrozumieniem reguł dotyczących cech w typach danych algebraicznych. Oto uproszczony przykład:Cechy w algebraicznych typach danych

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

Powyższe nie skompilować, otrzymując następujący komunikat o błędzie:

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

Dlaczego jest tak, że pond.push(duck) jest ważna, ale nie jest lake.push(mallard)? W obu przypadkach dostarczono Duck, gdzie oczekiwano Quack. W pierwszym kompilator jest szczęśliwy, ale w drugim nie.

Czy przyczyną tej różnicy jest CoerceUnsized?

+0

Uwaga: 'RefCell' jest tutaj zbędny, mogę odtworzyć problem z' Rc > '. –

+0

Matthieu, masz rację. "RefCell" nie jest konieczne, aby wystąpił błąd. Na podstawie odpowiedzi Vladimira poniżej, widzę, dlaczego ten sam błąd występuje z lub bez 'RefCell'. – rlkw1024

Odpowiedz

6

To zachowanie prawidłowe, nawet jeśli jest nieco niefortunne.

W pierwszym przypadku mamy to:

Zauważ, że push(), gdy wezwał Vec<Box<Quack>>, akceptuje Box<Quack>, a ty przechodząc Box<Duck>. Jest OK - rustc jest w stanie zrozumieć, że chcesz przekonwertować pudełkową wartość obiektu cechy, jak tutaj:

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

W drugim przypadku mamy to:

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

Tutaj push() akceptuje Rc<RefCell<Box<Quack>>> gdy podasz Rc<RefCell<Box<Duck>>>:

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

A teraz jest kłopot. Box<T> jest typem kompatybilnym z DST, więc może być używany jako kontener dla obiektu cechy. To samo będzie wkrótce prawdziwe dla Rc i innych inteligentnych wskaźników, gdy zaimplementowane zostanie this RFC. Jednak w tym przypadku nie ma przymusu od konkretnego typu do obiektu cechy, ponieważ Box<Duck> znajduje się wewnątrz dodatkowych warstw typów (Rc<RefCell<..>>).

Pamiętaj, że obiekt cech jest grubym wskaźnikiem, więc Box<Duck> różni się od rozmiaru. W związku z tym, w zasadzie, nie są one bezpośrednio kompatybilne: nie można po prostu wziąć bajtów z Box<Duck> i zapisać ich tam, gdzie oczekiwany jest Box<Quack>. Rust wykonuje specjalną konwersję, tzn. Uzyskuje wskaźnik do wirtualnej tabeli dla Duck, tworzy gruby wskaźnik i zapisuje go do zmiennej o zmiennej Box<Quack>.

Kiedy masz Rc<RefCell<Box<Duck>>> jednak rustc musiałby wiedzieć, jak skonstruować i destructure zarówno RefCell i Rc aby zastosować ten sam wskaźnik konwersji tłuszczu do swoich wewnętrznych. Naturalnie, ponieważ są to typy bibliotek, nie mogą wiedzieć, jak to zrobić. Dotyczy to również każdego innego typu opakowania, np. Arc lub Mutex lub nawet Vec. Nie spodziewasz się, że będzie można użyć Vec<Box<Duck>> jako Vec<Box<Quack>>, prawda?

Nie ma też fakt, że w tym przykładzie z Rc RCS stworzony z Box<Duck> i Box<Quack> nie zostałyby połączone - to oni mieli różne liczniki odniesienia.

Oznacza to, że konwersja z konkretnego typu do obiektu cechy może nastąpić tylko wtedy, gdy masz bezpośredni dostęp do inteligentnego wskaźnika, który obsługuje DST, a nie, gdy jest ukryty w innej strukturze.

To powiedziawszy, widzę, jak to może być możliwe, aby umożliwić to dla kilku wybranych typów. Na przykład, możemy wprowadzić pewne cechy, które są znane kompilatorowi i które mogłyby wykorzystać do "dotarcia" wewnątrz stosu owijki i przeprowadzenia wewnątrz nich konwersji cech charakterystycznych. Jednak nikt nie zaprojektował tej rzeczy i dostarczył jej jeszcze RFC - prawdopodobnie dlatego, że nie jest to funkcja bardzo potrzebna.

+0

Dzięki! Jak wdrożysz ten wzór? Wzór, który mam na myśli, brzmi następująco: masz mnóstwo zmiennych obiektów. Aby uzyskać wydajność, można uzyskać do nich dostęp za pośrednictwem różnych struktur wyszukiwania, np. hash, binarna taca i wektor. Niektóre z tych struktur wyszukiwania wymagają obiektów różnych typów, które wszystkie implementują cechy. Mogłem zawinąć je w Enums. Mogę też utworzyć strukturę opakowania zawierającą pola. – rlkw1024

+0

Zgodnie z tym dokumentem RFC (https://github.com/rust-lang/rfcs/blob/master/text/0982-dst-coercion.md) typ opakowania docelowego powinien implementować funkcję CoerceUnsized, aby umożliwić automatyczne wywoływanie cechy . Jednak RefCell w tej chwili pomija tę implementację, ale nadal jest godne zaufania dla jednostek stosowych (https://stackoverflow.com/questions/30861295/how-to-i-pass-rcrefcellboxmystruct-to-a-function-accepting-rcrefcellbox). Box implementuje CoerceUnsized i jest samodzielny, ale nie w RefCell, co jest kompletnie dziwaczne. – snuk182

+0

starałem się realizować własne RefCell który implementuje CoerceUnsized, bez powodzenia (https://play.rust-lang.org/?gist=27e5b540bfceaa9db79abea5f6526d48&version=nightly&backtrace=2), a to jest jeszcze bardziej jasne, dlaczego jest tak, powodują RefCell to po prostu narzędzie do sprawdzania wypożyczeń dla UnsafeCell, które jest po prostu opakowaniem takim jak QuackWrap z poniższego przykładu. – snuk182

1

Vladimir's answer wyjaśnił co kompilator robi. Na podstawie tych informacji opracowałem rozwiązanie: Tworzenie otoki o strukturze zgodnej z Box<Quack>.

Owijka nazywa QuackWrap. Ma stały rozmiar i może być używany tak jak każda inna struktura (jak sądzę). Box wewnątrz QuackWrap pozwala mi budować QuackWrap wokół każdej cechy, która implementuje Quack. W ten sposób mogę mieć Vec<Rc<RefCell<QuackWrap>>> gdzie wartości wewnętrzne są mieszaniną Duck s, Goose s, itd

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

jako dodatkowej wygody, prawdopodobnie będę chciał realizować Deref i DefrefMut na QuackWrap. Ale nie jest to konieczne w powyższym przykładzie.