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)
};
}
这是一个好的解决方案吗?正确吗?
答案 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>
。