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

Cés*_*cón 4 generics thread-safety rust phantom-types

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

pub struct Map<T> {
    filename: String,
    phantom: PhantomData<*const T>,
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

// Given a master_map of type Arc<Map<Region>> ...
let map = Arc::clone(&master_map);

thread::spawn(move || {
    map.do_stuff();
});
Run Code Online (Sandbox Code Playgroud)

即使没有移动Region值甚至指针也无法编译:

pub struct Map<T> {
    filename: String,
    phantom: PhantomData<*const T>,
}
Run Code Online (Sandbox Code Playgroud)

这是操场上的一个完整的测试用例,它展示了这个问题

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();
}
Run Code Online (Sandbox Code Playgroud)

处理这个问题的最佳方法是什么?这是我尝试过的:

将虚拟场定义为PhantomData<T>一个适当的值而不是一个指针。这有效,但我对此持谨慎态度,因为根据上面引用的文档,我不知道它对 Rust 编译器的“删除检查分析”有什么影响(如果有的话)。

将虚拟场定义为PhantomData<&'a T>一个参考。这应该可以工作,但它会强制结构采用不需要的生命周期参数,该参数会通过我的代码传播。我宁愿不这样做。

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

unsafe impl<T> Sync for Map<T> {}
unsafe impl<T> Send for Map<T> {}
Run Code Online (Sandbox Code Playgroud)

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

澄清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.
}
Run Code Online (Sandbox Code Playgroud)

Fra*_*gné 6

还有一个选择:PhantomData<fn() -> T>。与andfn() -> T具有相同的差异,但不同的是,它同时实现了and 。这也清楚地表明,你的结构永远只能产生的实例。(如果某些方法作为输入,那么可能更合适)。T*const T*const TSendSyncTTPhantomData<fn(T) -> T>

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