Как предотвратить автоимплементацию синхронизации
У меня есть структура, содержащая небезопасный код со следующим методом:
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
реализуется по умолчанию, что я хотел бы предотвратить.
Я нашел два способа сделать это, и оба они не так уж велики...
-
Добавьте поле struct, которое не реализует
Sync
например*const u8
, это очевидно довольно плохо, так как это приводит к ненужному и неясному коду и не ясно показывает мои намерения. -
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 ответ:
Внутренняя изменчивость в ржавчине требует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"]
.