2017-07-29 116 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"]所示。