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)
这是一个好的解决方案吗?这是对的吗?
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)
此代码首先生成ptr
a *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以获取更多详细信息.
我认为你的解决方案是正确的,而我不是不安全代码的专家.但是,您不必使用不安全的代码来执行向上转换等简单的操作:
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>
.