Кастинг Rc <ConcreteType>к Rc <Trait> - PullRequest
0 голосов
/ 13 ноября 2018

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)
    };
}

Это хорошее решение? Это правильно?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

Ответ от 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, так почему компилятор принимает код? Ответ заключается в том, что компилятор выполняет нестандартное приведение , тип неявного приведения, который выполняется в определенных местах для определенных типов . В частности, мы конвертируем указатель на конкретный тип в указатель на любой тип , реализующий черту Animal. Поскольку мы не знаем точный тип, теперь указатель больше не является простым адресом памяти - это адрес памяти вместе с идентификатором фактического типа объекта, так называемый толстый указатель, Таким образом, 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 a *const Horse, печатает размер указателя, затем использует нестандартное принуждение для преобразования ptr в *const Animal и снова печатает его размер. В 64-битной системе будет напечатано

8
16

Первый - это просто адрес памяти, а второй - адрес памяти вместе с информацией о конкретном типе объекта. (В частности, толстый указатель содержит указатель на таблицу виртуальных методов .)

Теперь давайте посмотрим, что происходит в коде в ответе Боэтиоса

let animal = horse as Rc<Animal>;

или эквивалентно

let animal: Rc<Animal> = horse;

также выполнить несвязанное принуждение. Как компилятор знает, как это сделать для Rc, а не для необработанного указателя? Ответ таков: признак CoerceUnsized существует специально для этой цели . Вы можете прочитать RFC о принуждениях для динамически изменяемых типов для получения дополнительной информации.

0 голосов
/ 13 ноября 2018

Я думаю , что ваше решение верное, хотя я не специалист по небезопасному коду.Но вам не нужно использовать небезопасный код для выполнения таких простых вещей, как апкастинг:

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>.

...