Как разделить структуру, содержащую фантомный указатель, между потоками? - PullRequest
0 голосов
/ 06 мая 2018

У меня есть структура, которая должна быть общей для типа, но тип фактически не содержится в структуре: он используется в методах этой структуры, а не в самой структуре. Итак, структура включает 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.
}

Ответы [ 2 ]

0 голосов
/ 06 мая 2018

Есть еще один вариант: PhantomData<fn() -> T>. fn() -> T имеет то же отклонение , что и T и *const T, но в отличие от *const T, оно реализует Send и Sync. Это также дает понять, что ваша структура только создает экземпляров T. (Если некоторые методы принимают T в качестве входных данных, тогда PhantomData<fn(T) -> T> может быть более подходящим).

#[derive(Debug)]
struct Map<T> {
    filename: String,
    phantom: PhantomData<fn() -> T>,
}
0 голосов
/ 06 мая 2018

черты маркера нулевого размера

Мое предпочтительное решение состоит в том, чтобы использовать для этой цели одноразовые структуры:

#[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"));
    // ...
}

площадка

структура, которая должна быть общей для типа, но тип фактически не содержится в структуре: она используется в методах этой структуры, а не в самой структуре.

Мое обоснование заключается в том, что вам на самом деле не нужно параметризовать вашу структуру по типу , используемому в методах , вам просто нужно параметризовать ее по некоторому типу. Это лучший случай для того, чтобы иметь свою собственную черту. Это, вероятно, еще более мощно, поскольку вы можете иметь связанные типы или константы в реализации черты.

Узкая реализация

но эти 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> {}

Это предотвращает «случайную» реализацию этих черт для вашего типа, если какое-то другое поле не является Send или Sync.

детская площадка

...