这是采用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();
一切都很好,但是我要为此 map 编写的某些迭代器(例如仅提供值的迭代器)在其类型中不包含标记。接下来的转换会安全地丢弃标记吗?
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, ()>,
}
最佳答案
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)
,除非您不愿意支付这笔费用(例如,因为您丢失了编译器的自动按顺序减小尺寸,并且无法手动复制它)。
关于rust - 转换PhantomData标记是否安全?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52911942/