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

dil*_*van 0 collections polymorphism traits rust

在像Clojure这样的动态语言中,很容易表达不同类型的集合:

{:key1 "foo", :key2 [34 "bar" 4.5], "key3" {:key4 "foobar"}}
Run Code Online (Sandbox Code Playgroud)

在Rust中,实现此类集合的首选方法是使用特征对象或枚举.使用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>());
Run Code Online (Sandbox Code Playgroud)

这种方法似乎是不鼓励的.它的缺点是什么?

Pet*_*all 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!"),
}
Run Code Online (Sandbox Code Playgroud)

请注意,我必须明确地向下转换为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"
Run Code Online (Sandbox Code Playgroud)

我能够在不知道具体类型的情况下使用所有值,因为我只使用了它们实现的事实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),
}
Run Code Online (Sandbox Code Playgroud)

在这里,我仍然需要知道所有类型,但至少会有一个编译器警告,如果我错过了一个.另请注意,枚举可以拥有其值,因此(除了&str在这种情况下)数据可以紧密地打包在内存中,使访问速度更快.

总之,Any对于异构集合来说很少是正确的答案,但是特征对象和枚举都有自己的权衡.