generics - 如何在线程之间共享包含幻像指针的结构?
问题描述
我有一个需要对类型通用的结构,但该类型实际上并未包含在结构中:它用于此结构的方法中,而不是结构本身。因此,该结构包括一个PhantomData
成员:
pub struct Map<T> {
filename: String,
phantom: PhantomData<*const T>,
}
幻像成员被定义为指针,因为该结构实际上并不拥有 type 的数据T
。这是根据文档中的std::marker::PhantomData
建议:
添加类型字段
PhantomData<T>
表示您的类型拥有类型数据T
。这反过来意味着当你的类型被删除时,它可能会删除一个或多个 type 实例T
。这与 Rust 编译器的 drop check 分析有关。如果您的结构实际上不拥有 type 的数据,
T
最好使用引用类型,例如PhantomData<&'a T>
(理想情况下)或PhantomData<*const T>
(如果没有生命周期适用),以免表明所有权。
所以这里的指针似乎是正确的选择。然而,这会导致结构不再是Send
nor Sync
,因为PhantomData
is onlySend
并且Sync
如果它的类型参数是,并且由于指针既不是,整个事情也不是。所以,像这样的代码
// Given a master_map of type Arc<Map<Region>> ...
let map = Arc::clone(&master_map);
thread::spawn(move || {
map.do_stuff();
});
即使没有Region
移动任何值甚至指针也无法编译:
error[E0277]: the trait bound `*const Region: std::marker::Send` is not satisfied in `Map<Region>`
--> src/main.rs:57:9
|
57 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `*const Region` cannot be sent between threads safely
|
= help: within `Map<Region>`, the trait `std::marker::Send` is not implemented for `*const Region`
= note: required because it appears within the type `std::marker::PhantomData<*const Region>`
= note: required because it appears within the type `Map<Region>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<Map<Region>>`
= note: required because it appears within the type `[closure@src/main.rs:57:23: 60:10 map:std::sync::Arc<Map<Region>>]`
= note: required by `std::thread::spawn`
error[E0277]: the trait bound `*const Region: std::marker::Sync` is not satisfied in `Map<Region>`
--> src/main.rs:57:9
|
57 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `*const Region` cannot be shared between threads safely
|
= help: within `Map<Region>`, the trait `std::marker::Sync` is not implemented for `*const Region`
= note: required because it appears within the type `std::marker::PhantomData<*const Region>`
= note: required because it appears within the type `Map<Region>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<Map<Region>>`
= note: required because it appears within the type `[closure@src/main.rs:57:23: 60:10 map:std::sync::Arc<Map<Region>>]`
= note: required by `std::thread::spawn`
use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::Arc;
use std::thread;
#[derive(Debug)]
struct Region {
width: usize,
height: usize,
// ... more stuff that would be read from a file
}
#[derive(Debug)]
struct Map<T> {
filename: String,
phantom: PhantomData<*const T>,
}
// General Map methods
impl<T> Map<T>
where
T: Debug,
{
pub fn new<S>(filename: S) -> Self
where
S: Into<String>,
{
Map {
filename: filename.into(),
phantom: PhantomData,
}
}
pub fn do_stuff(&self) {
println!("doing stuff {:?}", self);
}
}
// Methods specific to Map<Region>
impl Map<Region> {
pub fn get_region(&self) -> Region {
Region {
width: 10,
height: 20,
}
}
}
fn main() {
let master_map = Arc::new(Map::<Region>::new("mapfile"));
master_map.do_stuff();
let region = master_map.get_region();
println!("{:?}", region);
let join_handle = {
let map = Arc::clone(&master_map);
thread::spawn(move || {
println!("In subthread...");
map.do_stuff();
})
};
join_handle.join().unwrap();
}
处理这个问题的最佳方法是什么?这是我尝试过的:
将幻象场定义为PhantomData<T>
。一个合适的值而不是一个指针。这可行,但我对此持谨慎态度,因为根据上面引用的文档,我不知道它对 Rust 编译器的“drop check analysis”有什么影响(如果有的话)。
将幻象场定义为PhantomData<&'a T>
。一个参考。这应该可行,但它强制结构采用不需要的生命周期参数,该参数通过我的代码传播。我宁愿不这样做。
强制结构实现Send
和Sync
。这就是我目前正在做的事情:
unsafe impl<T> Sync for Map<T> {}
unsafe impl<T> Send for Map<T> {}
它似乎有效,但那些unsafe impl
s 很丑,让我紧张。
澄清T
用途:没关系,真的。它甚至可能不被使用,只是作为类型系统的标记提供。例如,只需要它Map<T>
具有类型参数,因此impl
可以提供不同的块:
impl<T> struct Map<T> {
// common methods of all Maps
}
impl struct Map<Region> {
// additional methods available when T is Region
}
impl struct Map<Whatever> {
// additional methods available when T is Whatever, etc.
}
解决方案
还有另一种选择:PhantomData<fn() -> T>
. 与andfn() -> T
具有相同的方差,但与 不同的是,它同时实现了and 。它还清楚地表明您的结构只生成. (如果某些方法作为输入,那么可能更合适)。T
*const T
*const T
Send
Sync
T
T
PhantomData<fn(T) -> T>
#[derive(Debug)]
struct Map<T> {
filename: String,
phantom: PhantomData<fn() -> T>,
}