我可以在两个特征之间进行转换吗?
Can I cast between two traits?
有没有办法从一个特征转换到另一个特征?
我有 Foo
和 Bar
以及 Vec<Box<dyn Foo>>
的特征。我知道 Vec
中的一些项目实现了 Bar
特性,但有什么方法可以定位它们吗?
我不知道这是否可能。
trait Foo {
fn do_foo(&self);
}
trait Bar {
fn do_bar(&self);
}
struct SomeFoo;
impl Foo for SomeFoo {
fn do_foo(&self) {
println!("doing foo");
}
}
struct SomeFooBar;
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
}
impl Bar for SomeFooBar {
fn do_bar(&self) {
println!("doing bar");
}
}
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
// if let Some(val) = foo.downcast_whatever::<Bar>() {
// val.bar();
// }
}
}
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b637bddc4fc923ce705e84ad1d783d4)
没有。无法在两个不相关的特征之间进行转换。要理解为什么,我们必须了解特征对象是如何实现的。首先,让我们看看 TraitObject
。
TraitObject
反映了 trait 对象的实际实现方式。它们由两个指针组成:data
和vtable
。 data
值只是对原始对象的引用:
#![feature(raw)]
use std::{mem, raw};
trait Foo {}
impl Foo for u8 {}
fn main() {
let i = 42u8;
let t = &i as &dyn Foo;
let to: raw::TraitObject = unsafe { mem::transmute(t) };
println!("{:p}", to.data);
println!("{:p}", &i);
}
vtable
指向 table 个函数指针。 table 包含对每个已实现特征方法的引用,按某种编译器内部方式排序。
对于这个假设输入
trait Foo {
fn one(&self);
}
impl Foo for u8 {
fn one(&self) { println!("u8!") }
}
table就是这样的伪代码
const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];
特征对象知道指向数据的指针和指向构成该特征的方法列表的指针。根据此信息,无法获取任何其他数据。
嗯,几乎没办法。正如您可能猜到的那样,您可以向 vtable 添加一个方法,即 returns 一个 different 特征对象。在计算机科学中,所有的问题都可以通过再增加一层间接来解决(除非间接层太多)。
另请参阅:
- Why doesn't Rust support trait object upcasting?
But couldn't the data
part of the TraitObject
be transmuted to the struct
不安全,不。特征对象不包含有关原始类型的信息。它所拥有的只是一个包含内存地址的原始指针。您可以 不安全地 将其转换为 &Foo
或 &u8
或 &()
,但编译器和运行时数据都不知道具体是什么原来的类型。
Any
trait 实际上也是通过跟踪原始结构的类型 ID 来实现的。如果您请求对正确类型的引用,该特征将为您转换数据指针。
Is there a pattern other than the one I described with my FooOrBar
trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different?
如果您拥有这些特征,那么您可以将 as_foo
添加到 Bar
特征,反之亦然。
您可以创建一个包含 Box<dyn Foo>
或 Box<dyn Bar>
的枚举,然后进行模式匹配。
您可以将 bar
的正文移动到 foo
的正文中以实现该实现。
您可以实现第三个特征 Quux
,其中调用 <FooStruct as Quux>::quux
调用 Foo::foo
并调用 <BarStruct as Quux>::quux
调用 Bar::foo
后跟 Bar::bar
。
所以...我不认为这正是您想要的,但这是我能得到的最接近的。
// first indirection: trait objects
let sf: Box<Foo> = Box::new(SomeFoo);
let sb: Box<Bar> = Box::new(SomeFooBar);
// second level of indirection: Box<Any> (Any in this case
// is the first Box with the trait object, so we have a Box<Box<Foo>>
let foos: Vec<Box<Any>> = vec![Box::new(sf), Box::new(sb)];
// downcasting to the trait objects
for foo in foos {
match foo.downcast::<Box<Foo>>() {
Ok(f) => f.do_foo(),
Err(other) => {
if let Ok(bar) = other.downcast::<Box<Bar>>() {
bar.do_bar();
}
}
}
}
请注意,我们可以将 SomeFooBar
称为 Box<Bar>
只是因为我们首先将其存储为 Box<Bar>
。所以这仍然不是您想要的(SomeFooBar
也是 Foo
,但您不能再将其转换为 Box<Foo>
,因此我们并没有真正将一个特征转换为另一个)
简短的回答是:目前语言中对向下转型的支持极其有限。
长话短说,出于技术和哲学原因,能够向下转换并不被视为高优先级:
- 从技术角度来看,即使不是所有情况,也有大多数情况的解决方法
- 从哲学的角度来看,向下转换会导致更脆弱的软件(因为您意外地开始依赖实现细节)
有多个提案,我本人 participated,但目前 none 已被选中,目前尚不清楚 Rust 是否会变得沮丧,或者它是否会做它的局限性.
与此同时,您基本上有两个解决方法:
使用TypeId
:每个类型都有一个关联的TypeId
值可以查询,然后可以构建一个类型擦除的容器如Any
和查询它持有的类型是否是一个特定的 X。在幕后 Any
将简单地检查这个 X 的 TypeId
与存储值的 TypeId
。
像您一样创建特定的 trait
。
后者更开放,特别是可以与特征一起使用,而前者仅限于具体类型。
这是我所做的。
我向 Foo
特性添加了一个 as_bar
方法,return 是一个 Option<&Bar>
。我为 return None
提供了一个默认实现,这样对于 Foo
不关心 Bar
.
的实现者来说几乎没有什么不便
trait Foo {
fn do_foo(&self);
fn as_bar(&self) -> Option<&dyn Bar> {
None
}
}
我将实现 Foo
和 Bar
的 SomeFooBar
结构的方法覆盖为 return Some(self)
:
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
fn as_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
}
这使得调用代码看起来很像我想要的样子。
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
if let Some(bar) = foo.as_bar() {
bar.do_bar();
}
}
}
我希望看到 Rust 将来在这方面有所改进,但这是我完全可以接受的解决方案。
我最初找到的唯一解决方案是引入带有显式转换器方法的第三个特征 FooOrBar
并为两种类型实现它。不过感觉它不是完成这项工作的正确工具。
trait FooOrBar {
fn to_bar(&self) -> Option<&dyn Bar>;
fn to_foo(&self) -> Option<&dyn Foo>;
}
impl FooOrBar for SomeFooBar {
fn to_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
fn to_foo(&self) -> Option<&dyn Foo> {
None
}
}
impl FooOrBar for SomeFoo {
fn to_bar(&self) -> Option<&dyn Bar> {
None
}
fn to_foo(&self) -> Option<&dyn Foo> {
Some(self)
}
}
fn main() {
let foos: Vec<Box<dyn FooOrBar>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.to_foo().map(|foo| foo.do_foo());
foo.to_bar().map(|foo| foo.do_bar());
}
}
有没有办法从一个特征转换到另一个特征?
我有 Foo
和 Bar
以及 Vec<Box<dyn Foo>>
的特征。我知道 Vec
中的一些项目实现了 Bar
特性,但有什么方法可以定位它们吗?
我不知道这是否可能。
trait Foo {
fn do_foo(&self);
}
trait Bar {
fn do_bar(&self);
}
struct SomeFoo;
impl Foo for SomeFoo {
fn do_foo(&self) {
println!("doing foo");
}
}
struct SomeFooBar;
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
}
impl Bar for SomeFooBar {
fn do_bar(&self) {
println!("doing bar");
}
}
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
// if let Some(val) = foo.downcast_whatever::<Bar>() {
// val.bar();
// }
}
}
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b637bddc4fc923ce705e84ad1d783d4)
没有。无法在两个不相关的特征之间进行转换。要理解为什么,我们必须了解特征对象是如何实现的。首先,让我们看看 TraitObject
。
TraitObject
反映了 trait 对象的实际实现方式。它们由两个指针组成:data
和vtable
。 data
值只是对原始对象的引用:
#![feature(raw)]
use std::{mem, raw};
trait Foo {}
impl Foo for u8 {}
fn main() {
let i = 42u8;
let t = &i as &dyn Foo;
let to: raw::TraitObject = unsafe { mem::transmute(t) };
println!("{:p}", to.data);
println!("{:p}", &i);
}
vtable
指向 table 个函数指针。 table 包含对每个已实现特征方法的引用,按某种编译器内部方式排序。
对于这个假设输入
trait Foo {
fn one(&self);
}
impl Foo for u8 {
fn one(&self) { println!("u8!") }
}
table就是这样的伪代码
const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];
特征对象知道指向数据的指针和指向构成该特征的方法列表的指针。根据此信息,无法获取任何其他数据。
嗯,几乎没办法。正如您可能猜到的那样,您可以向 vtable 添加一个方法,即 returns 一个 different 特征对象。在计算机科学中,所有的问题都可以通过再增加一层间接来解决(除非间接层太多)。
另请参阅:
- Why doesn't Rust support trait object upcasting?
But couldn't the
data
part of theTraitObject
be transmuted to the struct
不安全,不。特征对象不包含有关原始类型的信息。它所拥有的只是一个包含内存地址的原始指针。您可以 不安全地 将其转换为 &Foo
或 &u8
或 &()
,但编译器和运行时数据都不知道具体是什么原来的类型。
Any
trait 实际上也是通过跟踪原始结构的类型 ID 来实现的。如果您请求对正确类型的引用,该特征将为您转换数据指针。
Is there a pattern other than the one I described with my
FooOrBar
trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different?
如果您拥有这些特征,那么您可以将
as_foo
添加到Bar
特征,反之亦然。您可以创建一个包含
Box<dyn Foo>
或Box<dyn Bar>
的枚举,然后进行模式匹配。您可以将
bar
的正文移动到foo
的正文中以实现该实现。您可以实现第三个特征
Quux
,其中调用<FooStruct as Quux>::quux
调用Foo::foo
并调用<BarStruct as Quux>::quux
调用Bar::foo
后跟Bar::bar
。
所以...我不认为这正是您想要的,但这是我能得到的最接近的。
// first indirection: trait objects
let sf: Box<Foo> = Box::new(SomeFoo);
let sb: Box<Bar> = Box::new(SomeFooBar);
// second level of indirection: Box<Any> (Any in this case
// is the first Box with the trait object, so we have a Box<Box<Foo>>
let foos: Vec<Box<Any>> = vec![Box::new(sf), Box::new(sb)];
// downcasting to the trait objects
for foo in foos {
match foo.downcast::<Box<Foo>>() {
Ok(f) => f.do_foo(),
Err(other) => {
if let Ok(bar) = other.downcast::<Box<Bar>>() {
bar.do_bar();
}
}
}
}
请注意,我们可以将 SomeFooBar
称为 Box<Bar>
只是因为我们首先将其存储为 Box<Bar>
。所以这仍然不是您想要的(SomeFooBar
也是 Foo
,但您不能再将其转换为 Box<Foo>
,因此我们并没有真正将一个特征转换为另一个)
简短的回答是:目前语言中对向下转型的支持极其有限。
长话短说,出于技术和哲学原因,能够向下转换并不被视为高优先级:
- 从技术角度来看,即使不是所有情况,也有大多数情况的解决方法
- 从哲学的角度来看,向下转换会导致更脆弱的软件(因为您意外地开始依赖实现细节)
有多个提案,我本人 participated,但目前 none 已被选中,目前尚不清楚 Rust 是否会变得沮丧,或者它是否会做它的局限性.
与此同时,您基本上有两个解决方法:
使用
TypeId
:每个类型都有一个关联的TypeId
值可以查询,然后可以构建一个类型擦除的容器如Any
和查询它持有的类型是否是一个特定的 X。在幕后Any
将简单地检查这个 X 的TypeId
与存储值的TypeId
。像您一样创建特定的
trait
。
后者更开放,特别是可以与特征一起使用,而前者仅限于具体类型。
这是我所做的。
我向 Foo
特性添加了一个 as_bar
方法,return 是一个 Option<&Bar>
。我为 return None
提供了一个默认实现,这样对于 Foo
不关心 Bar
.
trait Foo {
fn do_foo(&self);
fn as_bar(&self) -> Option<&dyn Bar> {
None
}
}
我将实现 Foo
和 Bar
的 SomeFooBar
结构的方法覆盖为 return Some(self)
:
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
fn as_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
}
这使得调用代码看起来很像我想要的样子。
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
if let Some(bar) = foo.as_bar() {
bar.do_bar();
}
}
}
我希望看到 Rust 将来在这方面有所改进,但这是我完全可以接受的解决方案。
我最初找到的唯一解决方案是引入带有显式转换器方法的第三个特征 FooOrBar
并为两种类型实现它。不过感觉它不是完成这项工作的正确工具。
trait FooOrBar {
fn to_bar(&self) -> Option<&dyn Bar>;
fn to_foo(&self) -> Option<&dyn Foo>;
}
impl FooOrBar for SomeFooBar {
fn to_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
fn to_foo(&self) -> Option<&dyn Foo> {
None
}
}
impl FooOrBar for SomeFoo {
fn to_bar(&self) -> Option<&dyn Bar> {
None
}
fn to_foo(&self) -> Option<&dyn Foo> {
Some(self)
}
}
fn main() {
let foos: Vec<Box<dyn FooOrBar>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.to_foo().map(|foo| foo.do_foo());
foo.to_bar().map(|foo| foo.do_bar());
}
}