Ответ от 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 о принуждениях для динамически изменяемых типов для получения дополнительной информации.