[英]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
可以保留底层具体类型的信息,并且可以为Horse
的Animal
实现调用正确的方法(如果有的话;在你的例子中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.