如何在线程之间共享包含幻像指针的结构?

How do I share a struct containing a phantom pointer among threads?

我有一个结构需要在类型上通用,但该类型实际上并未包含在结构中:它用于此结构的方法,而不是结构本身。因此,该结构包含一个 PhantomData 成员:

pub struct Map<T> {
    filename: String,
    phantom: PhantomData<*const T>,
}

幻像成员被定义为指针,因为该结构实际​​上并不拥有 T 类型的数据。这是 the documentation of std::marker::PhantomData:

中的建议

Adding a field of type PhantomData<T> indicates that your type owns data of type T. This in turn implies that when your type is dropped, it may drop one or more instances of the type T. This has bearing on the Rust compiler's drop check analysis.

If your struct does not in fact own the data of type T, it is better to use a reference type, like PhantomData<&'a T> (ideally) or PhantomData<*const T> (if no lifetime applies), so as not to indicate ownership.

所以指针在这里似乎是正确的选择。但是,这会导致结构不再是 Send 也不再是 Sync,因为如果其类型参数是 PhantomData,则它只是 SendSync,并且由于指针既不是,整个事情也不是。所以,像这样的代码

// 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`

这是 complete test case in the playground that exhibits this issue:

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> 引用。这应该可行,但它会强制结构采用不需要的生命周期参数,该参数会在我的代码中传播。我宁愿不这样做。

强制结构实现SendSync这就是我目前正在做的事情:

unsafe impl<T> Sync for Map<T> {}
unsafe impl<T> Send for Map<T> {}

似乎可行,但是那些 unsafe impl 很难看,让我很紧张。

澄清一下 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.
}

零大小标记特征

我的首选解决方案是为此目的使用一次性结构:

#[derive(Debug)]
struct Map<T: ThingMarker> {
    filename: String,
    marker: T,
}

trait ThingMarker: Default {}

#[derive(Debug, Default)]
struct RegionMarker;
impl ThingMarker for RegionMarker {}

// General Map methods
impl<T: ThingMarker> Map<T>
where
    T: Debug,
{
    pub fn new<S>(filename: S) -> Self
    where
        S: Into<String>,
    {
        Map {
            filename: filename.into(),
            marker: Default::default(),
        }
    }
   // ...
}

impl Map<RegionMarker> {
    pub fn get_region(&self) -> Region { /* ... */ }
}

fn main() {
    let master_map = Arc::new(Map::<RegionMarker>::new("mapfile"));
    // ...
}

playground

a structure that needs to be generic over a type, yet the type is not actually contained in the structure: it's used in methods of this structure, not in the structure itself.

我的理由是,您实际上不需要通过方法中使用的类型 来参数化您的结构,您只需要通过 一些参数化它 类型。这是拥有自己特质的典型案例。它可能更强大,因为您可以在特征实现上关联类型或常量。

缩小实施范围

but those unsafe impls are ugly and make me nervous.

他们应该这样做。一个简单的修改是创建你自己的包装器类型,它严格地实现了这些特征:

// Pick a better name for this struct
#[derive(Debug)]
struct X<T>(PhantomData<*const T>);

impl<T> X<T> {
    fn new() -> Self {
        X(PhantomData)
    }
}

unsafe impl<T> Sync for X<T> {}
unsafe impl<T> Send for X<T> {}

如果其他字段不是 SendSync.

,这会阻止 "accidentally" 为您的类型实现这些特征

playground

还有一个选项:PhantomData<fn() -> T>fn() -> TT*const T 具有相同的 variance,但与 *const T 不同的是,它实现了 SendSync。它还清楚地表明,您的结构只会 产生 T 的实例。 (如果某些方法以 T 作为输入,那么 PhantomData<fn(T) -> T> 可能更合适)。

#[derive(Debug)]
struct Map<T> {
    filename: String,
    phantom: PhantomData<fn() -> T>,
}