转换PhantomData标记是否安全?

时间:2018-10-21 03:40:09

标签: rust unsafe

此为out of context,因此似乎有些怪异,但我具有以下数据结构:

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 :(得分:3)

TL; DR:添加#[repr(C)],您应该会很好。


这里有两个单独的问题:从返回类型返回有效数据的意义上,转换是否有效,以及整个事件是否违反了可能附加到所涉及类型的任何更高级别的不变式。 (以我的blog post的术语,您必须确保同时保持有效性和安全性不变。)

对于有效性不变式,您处于未知领域。编译器可能会决定布局Map<T, M>Map<T, ()>的方式大不相同,即data字段的偏移量可能不同,并且可能存在虚假填充。似乎不太可能,但是到目前为止,我们在这里保证的很少。讨论happening right now是我们可以而且想要保证的。我们有意避免对repr(Rust)做出过多保证,以免将自己描绘在一个角落。

您可以做的是将repr(C)添加到您的结构中,那么我相当确定您可以依靠ZST进行任何更改(但我要asked for clarification才能确定)。对于repr(C),我们为结构的布局提供了更多保证,这实际上是其全部目的。如果您想在结构布局上玩花样,则可能应该添加该属性。

对于更高级别的安全性不变式,您必须注意不要创建破损的Map并让该“泄漏”超出API边界(进入周围的安全代码),即,您不应返回Map的实例,该实例违反了您可能放置在其上的所有不变式。此外,PhantomData对方差和应注意的丢弃检查器有一些影响。由于要转换的类型是如此琐碎(您的标记类型不需要删除,即它们及其可传递字段都未实现Drop),我认为您不必为此担心任何问题。

请明确说明,repr(Rust)(默认设置)在我们确定要保证的内容之后也可以使用-忽略size-0-align-1类型(例如PhantomData)完全对我来说似乎是一个明智的保证。就我个人而言,尽管我仍然建议使用repr(C),除非您为此付出了您不愿支付的费用(例如,由于丢失了编译器,因此无法按顺序自动缩小尺寸,并且无法手动复制它)。