将Rc <concretetype>强制转换为Rc <trait>

时间:2018-11-13 08:20:11

标签: casting rust

Horse是实现Animal特性的结构。我有一个Rc<Horse>和一个需要接受Rc<Animal>的函数,所以我想从Rc<Horse>转换为Rc<Animal>

我这样做了:

use std::rc::Rc;

struct Horse;

trait Animal {}

impl Animal for Horse {}

fn main() {
    let horse = Rc::new(Horse);
    let animal = unsafe {
        // Consume the Rc<Horse>
        let ptr = Rc::into_raw(horse);
        // Now it's an Rc<Animal> pointing to the same data!
        Rc::<Animal>::from_raw(ptr)
    };
}

这是一个好的解决方案吗?正确吗?

2 个答案:

答案 0 :(得分:7)

answer by Boiethios已经解释了可以使用as显式执行向上转换,甚至可以在某些情况下隐式进行。我想在机制上添加更多细节。

我将首先说明为什么您的不安全代码可以正常工作。

let animal = unsafe {
    let ptr = Rc::into_raw(horse);
    Rc::<Animal>::from_raw(ptr)
};

unsafe块中的第一行使用horse并返回*const Horse,这是指向具体类型的指针。指针正是您所期望的-horse数据的内存地址(忽略了示例中Horse的大小为零且没有数据的事实)。在第二行中,我们称为Rc::from_raw();让我们看一下该函数的原型:

pub unsafe fn from_raw(ptr: *const T) -> Rc<T>

由于我们正在为Rc::<Animal>调用此函数,因此预期的参数类型为*const Animal。但是我们拥有的ptr类型为*const Horse,那么为什么编译器会接受代码?答案是编译器执行 unercable ,这是一种performed in certain places for certain types的隐式强制转换。具体来说,我们将指针转换为具体类型,将指针转换为实现Animal特性的 any 类型的指针。由于我们不知道确切的类型,因此指针不再只是一个内存地址,而是一个内存地址以及对象实际类型的标识符,即所谓的 fat指针。这样,从胖指针创建的Rc可以保留基础具体类型的信息,并且可以为Horse实现Animal调用正确的方法(如果有) ;在您的示例中Animal没有任何功能,但是如果有的话,这当然应该继续工作。)

通过打印它们的大小,我们可以看到两种指针之间的区别

let ptr = Rc::into_raw(horse);
println!("{}", std::mem::size_of_val(&ptr));
let ptr: *const Animal = ptr;
println!("{}", std::mem::size_of_val(&ptr));

此代码首先使ptr成为*const Horse,打印指针的大小,然后使用大小不定的强制将ptr转换为和*const Animal,然后再次打印其大小。在64位系统上,这将打印

8
16

第一个只是一个简单的内存地址,而第二个是一个内存地址以及有关指针对象具体类型的信息。 (具体来说,胖指针包含指向virtual method table的指针。)

现在让我们看看Boethios的答案中的代码发生了什么

let animal = horse as Rc<Animal>;

或等效地

let animal: Rc<Animal> = horse;

还执行不定大小的强制。编译器如何知道如何为Rc而不是原始指针执行此操作?答案是the trait CoerceUnsized exists specifically for this purpose。您可以阅读RFC on coercions for dynamically sized types了解更多详细信息。

答案 1 :(得分:6)

认为您的解决方案是正确的,而我不是不安全代码的专家。但是,您不必使用不安全的代码来完成诸如转换这样的简单操作:

use std::rc::Rc;

trait Animal {}

struct Horse;

impl Animal for Horse {}

fn main() {
    let horse = Rc::new(Horse);
    let animal = horse as Rc<Animal>;
}

如果要将其传递给函数,甚至不必强制转换:

fn gimme_an_animal(_animal: Rc<Animal>) {}

fn main() {
    let horse = Rc::new(Horse);
    gimme_an_animal(horse);
}

因为Horse实现了Animal,所以马是动物。您无需为铸造它做任何特殊的事情。请注意,这种转换是破坏性的,您不能从Rc<Horse>制作Rc<Animal>