Безопасно ли преобразовывать маркеры PhantomData? - PullRequest
0 голосов
/ 21 октября 2018

Это взято из контекста , поэтому это может показаться немного странным, но у меня есть следующая структура данных:

use std::marker::PhantomData;

pub struct Map<T, M=()> {
    data: Vec<T>,
    _marker: PhantomData<fn(M) -> M>,
}

Map - это ассоциативная карта, где ключи«помечено», чтобы не использовать ключи одной карты на другой, не связанной карте.Пользователи могут включить это, передавая некоторый уникальный тип, который они сделали как M, например:

struct PlayerMapMarker;
let mut player_map: Map<String, PlayerMapMarker> = Map::new();

Это все хорошо, но некоторые итераторы (например, те, которые дают только значения) я хочуНапишите для этой карты, не содержат маркера в своем типе.Будет ли следующий преобразователь безопасным для удаления маркера?

fn discard_marker<T, M>(map: &Map<T, M>) -> &Map<T, ()> {
    unsafe { std::mem::transmute(map) }
}

Чтобы я мог написать и использовать:

fn values(&self) -> Values<T> {
    Values { inner: discard_marker(self).iter() }
}

struct Values<'a, T> {
    inner: Iter<'a, T, ()>,
}

1 Ответ

0 голосов
/ 23 октября 2018

TL; DR: Добавьте #[repr(C)] и вам должно быть хорошо.


Здесь есть две отдельные проблемы: действителен ли трансмут в смысле возврата действительногоданные в возвращаемом типе и нарушает ли все это какие-либо инварианты более высокого уровня, которые могут быть присоединены к вовлеченным типам.(В терминологии моего сообщения в блоге вы должны убедиться, что инварианты действительности и безопасности соблюдаются.)

Для инварианта действительности вы находитесь на неизведанной территории.Компилятор может решить расположить Map<T, M> очень иначе, чем Map<T, ()>, то есть поле data может иметь другое смещение и может быть ложное заполнение.Это кажется маловероятным, но пока мы здесь гарантируем очень мало.Обсуждение того, что мы можем и хотим гарантировать, что происходит прямо сейчас .Мы намеренно хотим не давать слишком много гарантий о repr(Rust), чтобы не рисовать себя в углу.

Что вы можете сделать, это добавить repr(C) в вашу структуру, тогда я вполне уверен, что вы можете рассчитывать наЗСТ ничего не меняет (но я попросил уточнить просто чтобы быть уверенным).Для repr(C) мы предоставляем больше гарантий о том, как устроена структура, что фактически является ее целью.Если вы хотите разыгрывать трюки со структурной разметкой, вам, вероятно, следует добавить этот атрибут.

Для инварианта безопасности более высокого уровня вы должны быть осторожны, чтобы не создать сломанный Map и позволить этой «утечке» за пределыграницы вашего API (в окружающий безопасный код), то есть вы не должны возвращать экземпляр Map, который нарушает любые инварианты, которые вы могли бы на него надеть.Более того, PhantomData оказывает некоторое влияние на дисперсию и проверку на падение, о которых вам следует знать.Поскольку трансмутируемые типы являются настолько тривиальными (типы маркеров не требуют отбрасывания, т. Е. Все они и их переходные поля не реализуют Drop), я не думаю, что вам следует ожидать каких-либо проблем с этой стороны.

Для ясности, repr(Rust) (по умолчанию) может также подойти, если мы решим, что это то, что мы хотим гарантировать - и полное игнорирование типов size-0-align-1 (например, PhantomData) выглядит так:довольно разумная гарантия для меня.Лично я все же посоветовал бы использовать repr(C), если это не связано с расходами, которые вы не готовы платить (например, из-за того, что вы теряете компиляторы автоматически уменьшая размер за счет переупорядочения и не можете копировать его вручную).

...