如何在线程之间共享包含幻像指针的结构?
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
,则它只是 Send
和 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`
这是 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>
。 引用。这应该可行,但它会强制结构采用不需要的生命周期参数,该参数会在我的代码中传播。我宁愿不这样做。
强制结构实现Send
和Sync
。这就是我目前正在做的事情:
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"));
// ...
}
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 impl
s 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> {}
如果其他字段不是 Send
或 Sync
.
,这会阻止 "accidentally" 为您的类型实现这些特征
还有一个选项:PhantomData<fn() -> T>
。 fn() -> T
与 T
和 *const T
具有相同的 variance,但与 *const T
不同的是,它实现了 Send
和 Sync
。它还清楚地表明,您的结构只会 产生 个 T
的实例。 (如果某些方法以 T
作为输入,那么 PhantomData<fn(T) -> T>
可能更合适)。
#[derive(Debug)]
struct Map<T> {
filename: String,
phantom: PhantomData<fn() -> T>,
}
我有一个结构需要在类型上通用,但该类型实际上并未包含在结构中:它用于此结构的方法,而不是结构本身。因此,该结构包含一个 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 typeT
. This in turn implies that when your type is dropped, it may drop one or more instances of the typeT
. 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, likePhantomData<&'a T>
(ideally) orPhantomData<*const T>
(if no lifetime applies), so as not to indicate ownership.
所以指针在这里似乎是正确的选择。但是,这会导致结构不再是 Send
也不再是 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();
});
即使没有移动 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>
。 引用。这应该可行,但它会强制结构采用不需要的生命周期参数,该参数会在我的代码中传播。我宁愿不这样做。
强制结构实现Send
和Sync
。这就是我目前正在做的事情:
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"));
// ...
}
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 impl
s 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> {}
如果其他字段不是 Send
或 Sync
.
还有一个选项:PhantomData<fn() -> T>
。 fn() -> T
与 T
和 *const T
具有相同的 variance,但与 *const T
不同的是,它实现了 Send
和 Sync
。它还清楚地表明,您的结构只会 产生 个 T
的实例。 (如果某些方法以 T
作为输入,那么 PhantomData<fn(T) -> T>
可能更合适)。
#[derive(Debug)]
struct Map<T> {
filename: String,
phantom: PhantomData<fn() -> T>,
}