2015-01-29 13 views
11

Mam różne struktury, które wszystkie realizują tę samą cechę. Chcę rozgałęziać się pod pewnymi warunkami, decydując w czasie wykonywania, które z tych struktur utworzyć. Następnie, bez względu na to, którą gałąź podążyłem, chcę wywołać metody z tej cechy.Czy dozwolone są zmienne polimorficzne?

Czy to możliwe w Rust? Mam nadzieję, że uda mi się osiągnąć coś takiego (co nie kompiluje):

trait Barks { 
    fn bark(&self); 
} 

struct Dog; 

impl Barks for Dog { 
    fn bark(&self) { 
     println!("Yip."); 
    } 
} 

struct Wolf; 

impl Barks for Wolf { 
    fn bark(&self) { 
     println!("WOOF!"); 
    } 
} 

fn main() { 
    let animal: Barks; 
    if 1 == 2 { 
     animal = Dog; 
    } else { 
     animal = Wolf; 
    } 
    animal.bark(); 
} 

Odpowiedz

12

Tak, ale nie tak łatwo. To, co tam napisałeś, jest takie, że animal powinno być zmienną typu Barks, ale Barks jest cechą; opis interfejsu. Cechy nie mają statycznie zdefiniowanego rozmiaru, ponieważ może pojawić się dowolny rozmiar dowolnego rozmiaru i impl Barks. Kompilator nie ma pojęcia, jak duży by był animal.

Co trzeba zrobić, to dodać warstwę pośrednią. W tym przypadku można użyć Box, chociaż można również użyć rzeczy jak Rc lub zwykły odnośnikach:

fn main() { 
    let animal: Box<Barks>; 
    if 1 == 2 { 
     animal = Box::new(Dog); 
    } else { 
     animal = Box::new(Wolf); 
    } 
    animal.bark(); 
} 

Tutaj mam alokacji Dog lub Wolf na stercie, a następnie rzucając że aż do Box<Barks>. To jest rodzaj jak rzutowanie obiektu na interfejs w coś takiego jak C# lub Java lub rzutowanie Dog* do Barks* w C++.

Zupełnie inne podejście, które można zastosować, byłoby również wyliczeniem. Możesz mieć enum Animal { Dog, Wolf }, a następnie zdefiniować impl Animal { fn bark(&self) { ... } }. Zależy od tego, czy potrzebujesz całkowicie otwartego zestawu zwierząt i/lub wielu cech.

Na koniec zauważ, że "rodzaj" powyżej. Są różne rzeczy, które nie działają tak, jak w Javie/C#/C++. Na przykład Rust nie ma downcastingu (nie można przejść z Box<Barks> z powrotem do Box<Dog> lub od jednej cechy do drugiej). Działa to również wtedy, gdy cecha jest "bezpieczna dla obiektu" (brak generycznych, nie ma zastosowania jako wartość bezwzględna).

+0

Dzięki! Wspomniałeś, że mogę używać zwykłych referencji. Jak to może działać? – rlkw1024

+0

Zobacz moją odpowiedź dla zwykłych referencji. – Shepmaster

9

DK ma dobre wytłumaczenie, ja po prostu dostroić się z przykładem, gdzie przydzielenia Dog lub Wolf na stosie, unikając alokację sterty:

fn main() { 
    let dog; 
    let wolf; 
    let animal: &Barks; 
    if 1 == 2 { 
     dog = Dog; 
     animal = &dog; 
    } else { 
     wolf = Wolf; 
     animal = &wolf; 
    } 
    animal.bark(); 
} 

to trochę brzydki, ale referencje osiągnij ten sam kierunek co jako Box z mniejszą ilością smużenia.

3

Definiowanie niestandardowego wyliczenia jest najskuteczniejszym sposobem wykonania tego. Umożliwi to przydział na stosie dokładnie tyle miejsca, ile potrzebujesz, tj. Rozmiar największej opcji, plus 1 dodatkowy bajt do śledzenia, która opcja jest przechowywana. Pozwala także na bezpośredni dostęp bez poziomu pośredniego, w przeciwieństwie do rozwiązań wykorzystujących Box lub referencję cechy.

Niestety, to wymaga więcej kotła płytę:

enum WolfOrDog { 
    IsDog(Dog), 
    IsWolf(Wolf) 
} 
use WolfOrDog::*; 

impl Barks for WolfOrDog { 
    fn bark(&self) { 
     match *self { 
      IsDog(ref d) => d.bark(), 
      IsWolf(ref w) => w.bark() 
     } 
    } 
} 

fn main() { 
    let animal: WolfOrDog; 
    if 1 == 2 { 
     animal = IsDog(Dog); 
    } else { 
     animal = IsWolf(Wolf); 
    } 
    animal.bark(); 
} 

W main używamy tylko jednego stosu przydzielone zmienną, trzymając wystąpienie naszego niestandardowego wyliczenie.

+1

Myślę, że 'Canine' będzie lepszą nazwą dla enum. – Hauleth

Powiązane problemy