本文介绍了我可以在两个特质之间施放吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法从一个特征转换到另一个特征?

Is there a way to cast from one trait to another?

我有特征 FooBar 和一个 Vec>.我知道 Vec 中的一些项目实现了 Bar 特性,但有什么方法可以定位它们吗?

I have the traits Foo and Bar and a Vec<Box<dyn Foo>>. I know some of the items in the Vec implement the Bar trait, but is there any way I could target them?

我不明白这是否可能.

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)

推荐答案

没有.无法在两个不相关的特征之间进行转换.要了解原因,我们必须了解 trait 对象是如何实现的.首先,让我们看看 TraitObject.

No. There is no way to cast between two unrelated traits. To understand why, we have to understand how trait objects are implemented. To start with, let's look at TraitObject.

TraitObject 反映了 trait 对象是如何实际实现的.它们由两个指针组成:datavtable.data 值只是对原始对象的引用:

TraitObject is a reflection of how trait objects are actually implemented. They are composed of two pointers: data and vtable. The data value is just a reference to the original object:

#![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 指向一个函数指针表.此表包含对每个已实现 trait 方法的引用,按某种编译器内部方式排序.

vtable points to a table of function pointers. This table contains references to each implemented trait method, ordered by some compiler-internal manner.

对于这个假设的输入

trait Foo {
    fn one(&self);
}

impl Foo for u8 {
    fn one(&self) { println!("u8!") }
}

表格就是这样的伪代码

const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];

trait 对象知道一个指向数据的指针和一个指向组成该 trait 的方法列表的指针.根据这些信息,无法获得任何其他数据.

A trait object knows a pointer to the data and a pointer to a list of methods that make up that trait. From this information, there is no way to get any other piece of data.

好吧,几乎不可能.正如您可能猜到的,您可以向 vtable 添加一个返回 不同 trait 对象的方法.在计算机科学中,所有的问题都可以通过增加一个间接层来解决(除了太多的间接层).

Well, almost no way. As you might guess, you can add a method to the vtable that returns a different trait object. In computer science, all problems can be solved by adding another layer of indirection (except too many layers of indirection).

另见:

但是TraitObjectdata 部分不能转化为结构体

不安全,不.trait 对象不包含有关原始类型的信息.它所拥有的只是一个包含内存地址的原始指针.您可以不安全地将其转换为 &Foo&u8&(),但是编译器和运行时数据都不知道它最初是什么具体类型.

Not safely, no. A trait object contains no information about the original type. All it has is a raw pointer containing an address in memory. You could unsafely transmute it to a &Foo or a &u8 or a &(), but neither the compiler nor the runtime data have any idea what concrete type it originally was.

Any 特性 实际上是通过跟踪原始结构的类型 ID 来实现的.如果您请求正确类型的引用,特征将为您转换数据指针.

The Any trait actually does this by also tracking the type ID of the original struct. If you ask for a reference to the correct type, the trait will transmute the data pointer for you.

除了我用我的 FooOrBar 特征描述的模式之外,还有其他模式可以处理我们需要迭代一堆特征对象但对待其中一些特征对象略有不同的情况吗?

  • 如果您拥有这些特征,那么您可以将 as_foo 添加到 Bar 特征,反之亦然.

    • If you own these traits, then you can add as_foo to the Bar trait and vice versa.

      您可以创建一个包含 BoxBox 的枚举,然后进行模式匹配.

      You could create an enum that holds either a Box<dyn Foo> or a Box<dyn Bar> and then pattern match.

      对于该实现,您可以将 bar 的主体移动到 foo 的主体中.

      You could move the body of bar into the body of foo for that implementation.

      你可以实现第三个特征 Quux,其中调用 ::quux 调用 Foo::foo并调用 ::quux 调用 Bar::foo 后跟 Bar::bar.

      You could implement a third trait Quux where calling <FooStruct as Quux>::quux calls Foo::foo and calling <BarStruct as Quux>::quux calls Bar::foo followed by Bar::bar.

      这篇关于我可以在两个特质之间施放吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 00:31