将Rc <ConcreteType>转换为Rc <Trait>

Ned*_*eri 2 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)
    };
}
Run Code Online (Sandbox Code Playgroud)

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

Sve*_*ach 7

Boiethios答案已经解释了可以使用as甚至隐含地在某些情况下明确地执行向上转换.我想补充一些关于机制的更多细节.

我将首先解释为什么您的不安全代码正常工作.

let animal = unsafe {
    let ptr = Rc::into_raw(horse);
    Rc::<Animal>::from_raw(ptr)
};
Run Code Online (Sandbox Code Playgroud)

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

pub unsafe fn from_raw(ptr: *const T) -> Rc<T>
Run Code Online (Sandbox Code Playgroud)

因为我们正在调用此函数Rc::<Animal>,所以期望的参数类型是*const Animal.但是ptr我们有类型*const Horse,为什么编译器会接受代码呢?答案是编译器执行unsized强制,这是一种在某些类型的某些地方执行的隐式强制转换.具体来说,我们将指向具体类型的指针转​​换为指向实现特征的任何类型的指针Animal.由于我们不知道确切的类型,现在指针不再仅仅是一个内存地址 - 它是一个内存地址以及对象的实际类型的标识符,即所谓的胖指针.这样,Rc从fat指针创建的可以保留底层具体类型的信息,并且可以调用正确的方法来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));
Run Code Online (Sandbox Code Playgroud)

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

8
16
Run Code Online (Sandbox Code Playgroud)

第一个是简单的内存地址,而第二个是内存地址以及有关指针的具体类型的信息.(具体来说,胖指针包含指向虚方法表的指针.)

现在让我们看一下Boethios答案中代码中发生的事情

let animal = horse as Rc<Animal>;
Run Code Online (Sandbox Code Playgroud)

或者等价的

let animal: Rc<Animal> = horse;
Run Code Online (Sandbox Code Playgroud)

也执行未经证实的强制.编译器如何知道如何为Rc原始指针而不是原始指针执行此操作?答案是该特征CoerceUnsized专门用于此目的.您可以阅读关于动态大小类型的强制转换RFC以获取更多详细信息.


Fre*_*ios 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>;
}
Run Code Online (Sandbox Code Playgroud)

如果你想将它传递给一个函数,你甚至不需要强制转换:

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

fn main() {
    let horse = Rc::new(Horse);
    gimme_an_animal(horse);
}
Run Code Online (Sandbox Code Playgroud)

因为Horse工具Animal,马动物.你不需要做任何特殊的铸造它.请注意,此转换具有破坏性,您无法Rc<Horse>从中进行转换Rc<Animal>.

  • 你甚至可以做'让动物:Rc <动物> =马`. (2认同)