在异构Rust集合中使用Any trait对象有什么问题?

时间:2018-07-09 18:39:52

标签: collections rust polymorphism traits

在动态语言(如Clojure)中,很容易表达不同类型的集合:

{:key1 "foo", :key2 [34 "bar" 4.5], "key3" {:key4 "foobar"}}

在Rust中,实现此类集合的首选方法是using trait objects or enums。使用Any特征对象似乎是最灵活的方法(如果没有固定数量的已知类型替代方法),因为它可以向下转换为实际的对象类型:

let mut vector: Vec<Box<Any>> = Vec::new();
vector.push(Box::new("I’m"));
vector.push(Box::new(4 as u32));
console!(log, vector[0].downcast_ref::<&str>());
console!(log, vector[1].downcast_ref::<u32>());

似乎不鼓励这种方法。它有什么缺点?

1 个答案:

答案 0 :(得分:7)

  

它的缺点是什么?

主要缺点是,当您从&Any的集合中访问值时,唯一可以做的就是将其转换为特定的已知类型。如果存在您不知道的类型,则该类型的值是完全不透明的:您可以对它们进行什么操作,只计算有多少即可。如果您知道具体的类型,则可以向下转换,但是您将需要尝试每种可能的类型。

这是一个例子:

let a = 1u32;
let b = 2.0f32;
let c = "hello";

let v: Vec<&dyn Any> = vec![&a, &b, &c];

match v[0].downcast_ref::<u32>() {
    Some(x) => println!("u32: {:?}", x),
    None => println!("Not a u32!"),
}

请注意,我必须明确地向下转换为u32。使用这种方法将涉及每个可能的具体类型的逻辑分支,并且如果我忘记了情况,则不会出现编译器警告。

特质对象更具通用性,因为您不需要知道具体的类型就可以使用值-只要您仅坚持使用特征方法即可。

例如:

let v: Vec<&dyn Debug> = vec![&a, &b, &c];

println!("u32: {:?}", v[0]); // 1
println!("u32: {:?}", v[1]); // 2.0
println!("u32: {:?}", v[2]); // "hello"

我能够使用所有值而无需知道它们的具体类型,因为我只使用了它们实现Debug的事实。

这两种方法都有一个缺点,那就是在同类集合中使用具体类型:所有内容都隐藏在指针后面。访问数据始终是间接的,并且数据最终可能会散布在内存中,从而使访问效率大大降低,并且编译器更难以优化。

使用枚举使集合均匀,如下所示:

enum Item<'a> {
    U32(u32),
    F32(f32),
    Str(&'a str),
}

let v: Vec<Item> = vec![Item::U32(a), Item::F32(b), Item::Str(c)];
match v[0] {
    Item::U32(x) => println!("u32: {:?}", x),
    Item::F32(x) => println!("u32: {:?}", x),
    Item::Str(x) => println!("u32: {:?}", x),
}

在这里,我仍然必须了解所有类型,但是如果我错过了所有类型,至少会有一个编译器警告。还要注意,枚举可以拥有其值,因此(在这种情况下,除了&str之外,数据可以紧密地打包在内存中,从而可以更快地访问它。

总而言之,Any很少是异构集合的正确答案,但是特征对象和枚举都有各自的取舍。