Horse
is a struct which implements the Animal
trait. 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>
.
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. 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. 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). In the second line, we call 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
. Yet the ptr
we have has type *const Horse
, so why does the compiler accept the code? The answer is that the compiler performs an unsized coercion , a type of implicit cast that is performed in certain places for certain types . Specifically, we convert a pointer to a concrete type to a pointer to any type implementing the Animal
trait. 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).
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. On a 64-bit system, this will print
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
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? The answer is that the trait CoerceUnsized
exists specifically for this purpose . You can read the RFC on coercions for dynamically sized types for further details.
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. 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>
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.