【问题标题】:How do I share a struct containing a phantom pointer among threads?如何在线程之间共享包含幻像指针的结构?
【发布时间】:2018-05-06 13:24:43
【问题描述】:

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

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

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

添加PhantomData&lt;T&gt; 类型的字段表示您的类型拥有T 类型的数据。这反过来意味着当你的类型被删除时,它可能会删除一个或多个T 类型的实例。这与 Rust 编译器的 drop check 分析有关。

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

所以这里的指针似乎是正确的选择。然而,这会导致结构不再是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&lt;T&gt; 一个适当的值而不是指针。这可行,但我对此持谨慎态度,因为根据上面引用的文档,我不知道它对 Rust 编译器的“drop check analysis”有什么影响(如果有的话)。

将幻像字段定义为 PhantomData&lt;&amp;'a T&gt; 引用。这应该可行,但它强制结构采用不需要的生命周期参数,该参数通过我的代码传播。我宁愿不这样做。

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

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

似乎有效,但那些unsafe impls 很丑,让我紧张。

澄清T 的用途:这并不重要,真的。它甚至可能不被使用,只是作为类型系统的标记提供。例如。只需要让Map&lt;T&gt; 有一个类型参数,因此可以提供不同的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&lt;&amp;'static T&gt;,也许吧?或者...不,这需要T: 'static
  • 谢谢!但是,是的,'static 并不能很好地解决这个问题,这对类型来说是一个相当大的限制。另外,对于问T 用于什么以及为什么不参考的家伙:对不起,我想我删除了你的评论,试图删除我输入错误的回复>。
  • 所以我认为T的使用与问题无关,因为它没有在结构中使用,所以无论是Sync还是Send都没有区别.如果您查看我链接的操场示例,您会看到我使用它来定义一个 impl,其方法仅在 T 是特定类型时可用。所以这是一个可能的用途。我应该编辑我的问题吗?至于使用引用:这不太理想,因为必须将结构定义为struct Map&lt;'a, T&gt;,因此在使用它的任何地方都必须指定生命周期。没有意义的一生,因为它并没有真正被使用。
  • 太棒了,确实是我需要的,非常感谢。

标签: generics rust thread-safety phantom-types


【解决方案1】:

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

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

【讨论】:

    【解决方案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

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

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

    狭义的实现

    但是那些unsafe impls 很丑,让我紧张。

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

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

    【讨论】:

      猜你喜欢
      • 2018-07-30
      • 2013-11-28
      • 2013-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多