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

时间:2018-05-06 13:24:43

标签: generics rust thread-safety phantom-types

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

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

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

  

添加PhantomData<T>类型的字段表示您的类型拥有T类型的数据。这反过来意味着当您的类型被删除时,它可能会丢弃一个或多个类型为T的实例。这与Rust编译器的跌落检查分析有关。

     

如果您的结构实际上不拥有T类型的数据,最好使用引用类型,例如PhantomData<&'a T>(理想情况下)或PhantomData<*const T>(如果没有生命周期适用) ),以免表明所有权。

所以指针似乎是正确的选择。但是,这会导致结构不再是SendSync,因为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.
}

2 个答案:

答案 0 :(得分:3)

还有另一种选择:fn() -> TT*const T*const T具有相同的variance,但与Send不同,它同时实现了SyncT。它还清楚地表明,您的结构只能生成 T的实例。 (如果某些方法将PhantomData<fn(T) -> T>作为输入,则#[derive(Debug)] struct Map<T> { filename: String, phantom: PhantomData<fn() -> T>, } 可能更合适。)

{{1}}

答案 1 :(得分:2)

零大小的标记特征

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

#[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

  

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

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

缩小实施

  

但那些unsafe impl很难看,让我很紧张。

他们应该这样做。一个简单的修改是创建自己的包装器类型,以便狭隘地实现这些特性:

// 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,这可以防止“意外”为您的类型实现这些特征。

playground