Как предотвратить автоимплементацию синхронизации


У меня есть структура, содержащая небезопасный код со следующим методом:

use std::sync::Arc;
use std::thread;

#[derive(Debug)]
struct Foo<T> {
    items: Vec<Box<(T, String)>>,
}

impl<T> Foo<T> {
    pub fn add_element(&self, element: T, key: String) {
        if !(self.items.iter().any( |i| i.1 == key)) {
            let mut items = unsafe {change_mut(&(self.items))};
            items.push(Box::new((element,key)));
        }
    }
}

unsafe fn change_mut<T>(x: &T) -> &mut T { // changes &self to &mut self
    &mut *(x as *const T as *mut T)
}

fn main() {
    let foo = Arc::new(Foo { items: vec!() });
    let clone = foo.clone();

    // This should not be possible, as it might lead to UB
    thread::spawn(move || clone.add_element(1, String::from("one")));

    println!("{:?}", *foo);

}
Эта структура полностью безопасна до тех пор, пока кто-то не начнет использовать этот метод при многопоточности. Однако из-за того, что структура содержит только Vec<Box<T,String>>, Syncреализуется по умолчанию, что я хотел бы предотвратить. Я нашел два способа сделать это, и оба они не так уж велики...
  1. Добавьте поле struct, которое не реализует Sync например *const u8, это очевидно довольно плохо, так как это приводит к ненужному и неясному коду и не ясно показывает мои намерения.

  2. impl !Sync for Struct {} не доступен на stable и будет удален в соответствии с этой проблемой . Соответствующая ошибка говорит мне использовать вместо этого типы маркеров, ноdocumentation также не представляет способа решить мою проблему.


error: negative trait bounds are not yet fully implemented; use marker types for
 now (see issue #13231)
  --> srcholder.rs:44:1
   |
44 | impl !Sync for Struct {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^
1 2

1 ответ:

Внутренняя изменчивость в ржавчине требует1 использование UnsafeCell как намек компилятору, что обычные правила не применяются .

Поэтому ваша структура должна выглядеть так:

#[derive(Debug)]
struct Foo<T> {
    items: UnsafeCell<Vec<Box<(T, String)>>>,
}

И затем, реализация add_element настраивается на:

impl<T> Foo<T> {
    pub fn add_element(&self, element: T, key: String) {
        if !(self.items.iter().any( |i| i.1 == key)) {
            let mut items = unsafe { &mut *self.items.get() };
            //                       ^~~~~~~~~~~~~~~~~~~~~~
            items.push(Box::new((element,key)));
        }
    }
}

Использование UnsafeCell делает change_mut совершенно ненужным: в конце концов, это цель UnsafeCell, чтобы позволить внутреннюю изменчивость. Обратите внимание, как его метод get возвращает необработанный указатель, который не может быть разыменован без блока unsafe.


Так как UnsafeCell не реализует Sync, Foo<T> также не будет реализован Sync, и поэтому становится ненужным использовать отрицательные реализации или какой-либо маркер.


1Если вы не используете его непосредственно, скорее всего, абстракции, которые вы используете, построены на нем. Он настолько особенный, насколько это возможно: это элемент lang, обозначаемый его атрибутом #[lang = "unsafe_cell"].