2015-01-29 3 views
11

У меня есть разные структуры, которые реализуют один и тот же признак. Я хочу разветвиться на каком-то условии, решив во время выполнения, какую из этих структур создать. Затем, независимо от того, в какой ветке я следил, я хочу вызвать методы из этой черты.Разрешены ли полиморфные переменные?

Возможно ли это в ржавчине? Я надеюсь достичь чего-то вроде следующего (который не компилируется):

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

ответ

12

Да, но не так легко. То, что вы там написали, состоит в том, что animal должно быть переменной типа Barks, но Barks - признак; описание интерфейса. Черты не имеют статически определенного размера, так как может возникнуть тип любого размера и impl Barks. Компилятор понятия не имеет, как большой сделать animal.

Что вам нужно сделать, это добавить слой косвенности. В этом случае, вы можете использовать Box, хотя вы можете также использовать такие вещи, как Rc или простые ссылки:

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

Здесь я выделения Dog или Wolf в куче, то кастинг, что до Box<Barks>. Это вид как литье объекта в интерфейс в чем-то вроде C# или Java или литье Dog* в Barks* на C++.

Совершенно другой подход, который вы могли бы использовать, - это перечисления. У вас может быть enum Animal { Dog, Wolf }, а затем определить impl Animal { fn bark(&self) { ... } }. Зависит от того, нужен ли вам совершенно открытый набор животных и/или несколько признаков.

Наконец, обратите внимание, что «вид» выше. Существуют различные вещи, которые не работают, как в Java/C#/C++. Например, у Rust нет downcasting (вы не можете перейти от Box<Barks> к Box<Dog> или от одного признака к другому). Кроме того, это работает только в том случае, если признак «объект безопасен» (никаких дженериков, без использования self или Self по значению).

+0

Спасибо! Вы упомянули, что я могу использовать простые ссылки. Как это может сработать? – rlkw1024

+0

См. Мой ответ для простых ссылок. – Shepmaster

9

DK имеет хорошее объяснение, я просто перезвон в примере, где мы Выделяют Dog или Wolf в стеке, избегая выделения кучи:

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

Это немного некрасиво, но ссылки выполняют ту же самую направленность, что и Box с меньшим накладным минимумом.

3

Определение пользовательского перечисления является наиболее эффективным способом для этого. Это позволит вам выделять в стеке ровно столько пространства, которое вам нужно, то есть размер самой большой опции, плюс 1 дополнительный байт, чтобы отслеживать, какая опция хранится. Он также обеспечивает прямой доступ без уровня косвенности, в отличие от решений, использующих Box или ссылку на свойство.

К сожалению, это требует больше шаблонного:

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

В main мы используем только один стек выделяется переменным, держа экземпляр нашего пользовательского перечисления.

+1

Я думаю, что «Canine» будет лучшим именем для перечисления. – Hauleth

Смежные вопросы