简体   繁体   English

铸造Rc <ConcreteType> 到一个Rc <Trait>

[英]Casting Rc<ConcreteType> to an Rc<Trait>

Horse is a struct which implements the Animal trait. Horse是一个实现Animal特性的结构。 I have an Rc<Horse> and a function that needs to take in an Rc<Animal> , so I want to convert from Rc<Horse> to Rc<Animal> . 我有一个Rc<Horse>和一个需要接受Rc<Animal>的函数,所以我想从Rc<Horse>转换为Rc<Animal>

I did this: 我这样做了:

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)
    };
}

Is this a good solution? 这是一个好的解决方案吗? Is it correct? 这是正确的吗?

The answer by Boiethios already explains that upcasting can be explicitly performed using as , or even happens implicitly in certain situaions. Boiethios答案已经解释了可以使用as或者甚至隐含地在某些情况下明确地执行向上转换。 I'd like to add a few more detail on the mechanisms. 我想补充一些关于机制的更多细节。

I'll start with explaining why your unsafe code works correctly. 我将首先解释为什么您的不安全代码正常工作。

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

The first line in the unsafe block consumes horse and returns a *const Horse , which is a pointer to a concrete type. unsafe块中的第一行消耗horse并返回*const Horse ,它是指向具体类型的指针。 The pointer is exactly what you'd expect it to be – the memory address of horse 's data (ignoring the fact that in your example Horse is zero-sized and has no data). 指针正是你所期望的 - horse的数据的内存地址(忽略了在你的例子中Horse是零大小且没有数据的事实)。 In the second line, we call Rc::from_raw() ; 在第二行中,我们调用Rc::from_raw() ; let's look at the protoype of that function: 让我们看一下该函数的原型:

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

Since we are calling this function for Rc::<Animal> , the expected argument type is *const Animal . 由于我们为Rc::<Animal>调用此函数,因此预期的参数类型为*const Animal Yet the ptr we have has type *const Horse , so why does the compiler accept the code? 然而我们的ptr有类型*const Horse ,为什么编译器会接受代码? The answer is that the compiler performs an unsized coercion , a type of implicit cast that is performed in certain places for certain types . 答案是编译器执行unsized强制 ,这是一种在某些类型的某些地方执行的隐式强制转换 Specifically, we convert a pointer to a concrete type to a pointer to any type implementing the Animal trait. 具体来说,我们将指向具体类型的指针转​​换为指向实现Animal特征的任何类型的指针。 Since we don't know the exact type, now the pointer isn't a mere memory address anymore – it's a memory address together with an identifier of the actual type of the object, a so-called fat pointer . 由于我们不知道确切的类型,现在指针不再仅仅是一个内存地址 - 它是一个内存地址以及对象的实际类型的标识符,即所谓的胖指针 This way, the Rc created from the fat pointer can retain the information of the underlying concrete type, and can call the correct methods for Horse 's implementation of Animal (if there are any; in your example Animal doesn't have any functions, but of course this should continue to work if there are). 这样,从胖指针创建的Rc可以保留底层具体类型的信息,并且可以为HorseAnimal实现调用正确的方法(如果有的话;在你的例子中Animal没有任何函数,但当然,如果有的话,这应该继续有效。

We can see the difference between the two kinds of pointer by printing their size 我们可以通过打印它们的大小来看到两种指针之间的区别

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));

This code first makes ptr a *const Horse , prints the size of the pointer, then uses an unsized coercion to convert ptr to and *const Animal and prints its size again. 这段代码首先使ptr a *const Horse ,打印指针的大小,然后使用unsized强制将ptr转换为和*const Animal并再次打印其大小。 On a 64-bit system, this will print 在64位系统上,将打印

8
16

The first one is just a simple memory address, while the second one is a memory address together with information on the concrete type of the pointee. 第一个是简单的内存地址,第二个是内存地址以及有关指针的具体类型的信息。 (Specifically, the fat pointer contains a pointer to the virtual method table .) (具体来说,胖指针包含指向虚方法表的指针。)

Now let's look at what happens in the code in Boethios' answer 现在让我们看一下Boethios答案中代码中发生的事情

let animal = horse as Rc<Animal>;

or equivalently 或者等价的

let animal: Rc<Animal> = horse;

also perform an unsized coercion. 也执行未经证实的强制。 How does the compiler know how to do this for a Rc rather than a raw pointer? 编译器如何知道如何为Rc而不是原始指针执行此操作? The answer is that the trait CoerceUnsized exists specifically for this purpose . 答案是CoerceUnsized特性专门用于此目的 You can read the RFC on coercions for dynamically sized types for further details. 您可以阅读关于动态大小类型的强制转换RFC以获取更多详细信息。

I think that your solution is correct, while I'm not a specialist for unsafe code. 认为你的解决方案是正确的,而我不是不安全代码的专家。 But, you do not have to use unsafe code to do such simple things as upcasting: 但是,您不必使用不安全的代码来执行向上转换等简单的操作:

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>;
}

If you want to pass it to a function, you do not even have to cast: 如果你想将它传递给一个函数,你甚至不需要强制转换:

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

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

Because Horse implements Animal , an horse is an animal. 因为Horse实现了Animal ,所以马动物。 You do not have to do anything special for casting it. 你不需要做任何特殊的铸造它。 Note that this transformation is destructive, and you cannot make an Rc<Horse> from an Rc<Animal> . 请注意,此转换具有破坏性,您无法从Rc<Animal>创建Rc<Horse> Rc<Animal>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM