我可以对 trait 对象进行类型自省,然后向下转型吗?
Can I do type introspection with trait objects and then downcast it?
我有一个 Trait
的集合,一个迭代它并做一些事情的函数,然后我想检查实现类型,如果它是 Foo
类型,那么向下转换它并调用一些 Foo 方法。
基本上,类似于 Go 的 type-switch and interface conversion。
四处搜索我发现了 Any trait 但它只能在 'static
类型上实现。
为了帮助展示我想要的东西:
let vec: Vec<Box<Trait>> = //
for e in vec.iter() {
e.trait_method();
// if typeof e == Foo {
// let f = e as Foo;
// f.foo_method();
//}
}
如您所见,向下转型仅适用于 Any
特征,是的,它仅支持 'static
数据。您可以找到最近关于为什么如此的讨论 here。基本上,为任意生命周期的引用实现反射是困难的。
也不可能(至少到目前为止)将 Any
与您的自定义特征轻松结合。但是,macro library for automatic implementation of Any
for your trait has recently been created. You can also find some discussion on it here.
这不是特定于 Rust 的问题,尽管词汇可能略有不同。解决这样的问题的理想方法,不仅是 Rust 中的特征,而且是任何语言中的特征,都是将所需的行为(在您的示例中为 foo_method
)添加到抽象接口(Trait
):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) {
// override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
在此示例中,我在 Trait
中放置了 foo_method
的默认实现,它什么都不做,因此您不必在每个 impl
中定义它,而只需适用的地方。你应该真的尝试在你求助于向下转型到具体类型之前完成上述工作,这有严重的缺点,几乎消除了首先拥有特征对象的优势。
也就是说,在某些情况下可能需要向下转型,Rust 确实支持它——尽管界面有点笨拙。您可以通过添加中间 upcast 到 &Any
:
来向下转换 &Trait
到 &Foo
use std::any::Any;
trait Trait {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
fn downcast<T: Trait + 'static>(this: &dyn Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any
必须是 Trait
中的方法,因为它需要访问具体类型。现在您可以尝试在 Trait
特征对象上调用 Foo
方法,如下所示 (complete playground example):
if let Some(r) = downcast::<Foo>(&**e) {
r.foo_method();
}
要使其工作,您必须指定您期望的类型 (::<Foo>
) 并使用 if let
来处理当引用的对象不是 Foo
的实例时发生的情况.除非你确切地知道什么具体类型,否则你不能将特征对象向下转换为具体类型。
如果你需要知道具体的类型,那么 trait 对象几乎毫无用处!您可能应该改用 enum
,这样如果您在某处忽略处理变体,就会出现编译时错误。此外,您不能将 Any
与非 'static
结构一起使用,因此如果任何 Foo
可能需要包含引用,则此设计是死胡同。如果可以的话,最好的解决方案是将 foo_method
添加到特征本身。
我有一个 Trait
的集合,一个迭代它并做一些事情的函数,然后我想检查实现类型,如果它是 Foo
类型,那么向下转换它并调用一些 Foo 方法。
基本上,类似于 Go 的 type-switch and interface conversion。
四处搜索我发现了 Any trait 但它只能在 'static
类型上实现。
为了帮助展示我想要的东西:
let vec: Vec<Box<Trait>> = //
for e in vec.iter() {
e.trait_method();
// if typeof e == Foo {
// let f = e as Foo;
// f.foo_method();
//}
}
如您所见,向下转型仅适用于 Any
特征,是的,它仅支持 'static
数据。您可以找到最近关于为什么如此的讨论 here。基本上,为任意生命周期的引用实现反射是困难的。
也不可能(至少到目前为止)将 Any
与您的自定义特征轻松结合。但是,macro library for automatic implementation of Any
for your trait has recently been created. You can also find some discussion on it here.
这不是特定于 Rust 的问题,尽管词汇可能略有不同。解决这样的问题的理想方法,不仅是 Rust 中的特征,而且是任何语言中的特征,都是将所需的行为(在您的示例中为 foo_method
)添加到抽象接口(Trait
):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) {
// override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
在此示例中,我在 Trait
中放置了 foo_method
的默认实现,它什么都不做,因此您不必在每个 impl
中定义它,而只需适用的地方。你应该真的尝试在你求助于向下转型到具体类型之前完成上述工作,这有严重的缺点,几乎消除了首先拥有特征对象的优势。
也就是说,在某些情况下可能需要向下转型,Rust 确实支持它——尽管界面有点笨拙。您可以通过添加中间 upcast 到 &Any
:
&Trait
到 &Foo
use std::any::Any;
trait Trait {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
fn downcast<T: Trait + 'static>(this: &dyn Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any
必须是 Trait
中的方法,因为它需要访问具体类型。现在您可以尝试在 Trait
特征对象上调用 Foo
方法,如下所示 (complete playground example):
if let Some(r) = downcast::<Foo>(&**e) {
r.foo_method();
}
要使其工作,您必须指定您期望的类型 (::<Foo>
) 并使用 if let
来处理当引用的对象不是 Foo
的实例时发生的情况.除非你确切地知道什么具体类型,否则你不能将特征对象向下转换为具体类型。
如果你需要知道具体的类型,那么 trait 对象几乎毫无用处!您可能应该改用 enum
,这样如果您在某处忽略处理变体,就会出现编译时错误。此外,您不能将 Any
与非 'static
结构一起使用,因此如果任何 Foo
可能需要包含引用,则此设计是死胡同。如果可以的话,最好的解决方案是将 foo_method
添加到特征本身。