我有各种结构都实现了相同的特性。我想在某些条件下进行分支,在运行时决定实例化哪些结构。然后,无论我遵循哪个分支,我都想从该特征中调用方法。
这可能在Rust吗?我希望实现类似下面的内容(不编译):
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();
}
答案 0 :(得分:16)
是的,但不是那么容易。你在那里写的是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之类的东西中将对象转换为接口,或者在C ++中将Dog*
转换为Barks*
。
您可以使用的完全不同的方法是枚举。您可以enum Animal { Dog, Wolf }
然后定义impl Animal { fn bark(&self) { ... } }
。取决于您是否需要一套完全开放的动物和/或多种特征。
最后,请注意上面的“种类”。有些东西不像Java / C#/ C ++那样有效。例如,Rust没有向下转换(你不能从Box<Barks>
回到Box<Dog>
,或从一个特征转到另一个特征。此外,这仅适用于特征是“对象安全”(无泛型,不使用self
或Self
按值)。
答案 1 :(得分:16)
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
相同的间接,而且开销较小。
答案 2 :(得分:4)
定义自定义枚举是执行此操作的最有效方法。这将允许您在堆栈上精确分配所需的空间量,即最大选项的大小,再加上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
中,我们只使用一个堆栈分配变量,并保存自定义枚举的实例。