Подтвердить что ты не робот

Как Rust осуществляет отражение?

Rust имеет свойство Any, но также имеет политику "не платите за то, что вы не используете". Как Rust реализует отражение?

Я предполагаю, что Rust использует ленивые теги. Каждый тип сначала не назначен, но позже, если экземпляр типа передается функции, ожидающей признак Any, типу присваивается TypeId.

Или, может быть, Rust помещает TypeId в каждый тип, который его экземпляр, возможно, передан этой функции? Наверное, первый был бы дорогим.

4b9b3361

Ответ 1

Прежде всего, у Руста нет рефлексии; отражение подразумевает, что вы можете получить информацию о типе во время выполнения, например, о полях, методах, интерфейсах, которые он реализует, и т.д. Вы не можете сделать это с помощью Rust. Самое близкое, что вы можете получить, явно реализует (или выводит) признак, предоставляющий эту информацию.

Каждый тип получает TypeId, назначенный ему во время компиляции. Поскольку наличие глобально упорядоченных идентификаторов является сложным, идентификатор представляет собой целое число, полученное из комбинации определения типа и различных метаданных о ящике, в котором он содержится. Другими словами: они не назначены в каком-либо порядке, они просто хэши различных бит информации, которые входят в определение типа. [1]

Если вы посмотрите на источник для характеристики Any, вы увидите единственную реализацию для Any:

impl<T: Reflect + 'static + ?Sized> Any for T {
    fn get_type_id(&self) -> TypeId { TypeId::of::<T>() }
}

(Оценки могут быть неофициально сведены к "всем типам, которые не заимствованы из чего-то другого".)

Вы также можете найти определение TypeId:

pub struct TypeId {
    t: u64,
}

impl TypeId {
    pub fn of<T: ?Sized + Reflect + 'static>() -> TypeId {
        TypeId {
            t: unsafe { intrinsics::type_id::<T>() },
        }
    }
}

intrinsics::type_id - это внутренняя функция, распознаваемая компилятором, которая при задании типа возвращает свой внутренний идентификатор типа. Этот вызов просто заменяется во время компиляции с литеральным идентификатором типа целого; здесь нет реального звонка. [2] Как TypeId знает, что такое идентификатор типа. TypeId, то это всего лишь обертка вокруг этого u64, чтобы скрыть детали реализации от пользователей. Если вы считаете это концептуально более простым, вы можете просто подумать о типе TypeId как о постоянном 64-битном целочисленном значении, которое компилятор просто знает во время компиляции.

Any пересылает это значение из get_type_id, что означает, что get_type_id действительно просто привязывает метод признака к соответствующему методу TypeId::of. Это просто, чтобы убедиться, что если у вас есть Any, вы можете узнать исходный тип TypeId.

Теперь для большинства типов реализовано Any, но это не означает, что на всех этих типах фактически реализована реализация Any в памяти. Фактически происходит то, что компилятор генерирует только фактический код для реализации типа Any, если кто-то пишет код, который его требует. [3] Другими словами, если вы никогда не используете реализацию Any для данного типа, компилятор никогда не будет генерировать его.

Вот как Rust выполняет "не плати за то, что вы не используете": если вы никогда не передаете данный тип как &Any или Box<Any>, тогда связанный код никогда не генерируется и никогда не занимает какое-либо место в ваш скомпилированный двоичный файл.


[1]: Разочарование означает, что тип TypeId может изменять значение в зависимости от того, как библиотека скомпилирована, до такой степени, что компиляция в качестве зависимости (в отличие от автономной сборки) вызывает TypeId для изменения.

[2]: Насколько я знаю. Я мог ошибаться в этом, но я был бы очень удивлен, если бы это произошло.

[3]: Обычно это относится к дженерикам в Rust.