У меня есть структура, которая должна быть общей для типа, но тип фактически не содержится в структуре: он используется в методах этой структуры, а не в самой структуре. Итак, структура включает PhantomData
член:
pub struct Map<T> {
filename: String,
phantom: PhantomData<*const T>,
}
Фантомный член определен как указатель, поскольку структура на самом деле не владеет данными типа T
. Это по рекомендации в документации std::marker::PhantomData
:
Добавление поля типа PhantomData<T>
означает, что вашему типу принадлежат данные типа T
. Это, в свою очередь, означает, что когда ваш тип отброшен, он может отбросить один или несколько экземпляров типа T
. Это имеет отношение к анализу проверки на падение компилятора Rust.
Если ваша структура на самом деле не владеет данными типа T
, лучше использовать ссылочный тип, например PhantomData<&'a T>
(в идеале) или PhantomData<*const T>
(если не применяется время жизни), чтобы не указать право собственности.
Таким образом, указатель кажется правильным выбором здесь. Это, однако, приводит к тому, что структура больше не будет 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`
Вот полный тестовый пример на игровой площадке, где демонстрируется эта проблема :
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, как указано в приведенных выше документах.
Определение фантомного поля как 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.
}