2017-07-29 110 views
2

的autoimplementation我有一個包含不安全的代碼用下面的方法一個結構:如何防止同步

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. 添加不執行Sync例如*const u8一個結構領域,這顯然是相當糟糕的,因爲它最終導致不必要和不明確的代碼,並沒有清楚地表明我的意圖。

  2. impl !Sync for Struct {}目前無法在馬鞍上銷售,根據this issue將被刪除。 相應的錯誤告訴我使用標記類型,但the documention也沒有提供解決我的問題的方法。


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

請嘗試提供[MCVE],最好是連接到操場。當不需要猜測什麼被省略時,測試解決方案的正確性就容易得多。 –

+1

(MCVE示例:https://play.rust-lang.org/?gist=bcfd5e96cfcd390de67bc738bd821108&version=stable) –

+3

不,即使使用單個線程,代碼也不完全安全。 UB將&T投射到&mut T.你應該爲此使用UnsafeCell,這也應該解決你的同步問題。 – BurntSushi5

回答

4

內飾易變性拉斯特需要 使用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完全沒有必要的:它的目的畢竟,允許內部可變性。請注意它的get方法如何返回原始指針,如果沒有unsafe塊,則無法解除引用。


由於UnsafeCell沒有實現SyncFoo<T>不會執行Sync任一,因此,不需要使用負的實施方式或任何標記物。


如果你不直接使用它,機會是你使用的是建立在它的抽象。它可能是特殊的:它是一個lang項目,如其屬性#[lang = "unsafe_cell"]所示。