2017-07-29 3 views
2

Ich habe eine Struktur unsicheren Code mit dem folgenden Verfahren enthalten:Wie autoimplementation von Sync verhindern

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); 

} 

Diese Struktur ist völlig sicher, bis jemand mit dieser Methode beginnt, während Multithreading. Da die Struktur jedoch nur eine Vec<Box<T,String>> enthält, ist standardmäßig Sync implementiert, was ich gerne verhindern würde.

Ich habe zwei Wege gefunden, dies zu tun, die beide nicht so toll ...

  1. ein struct Feld hinzufügen, die nicht Sync für *const u8 Beispiel nicht implementiert, das ist offensichtlich eher schlecht, wie es endet in unnötigen und unklaren Code und zeigt nicht deutlich meine Absicht.

  2. impl !Sync for Struct {} ist nicht verfügbar auf stabil und wird nach this issue entfernt werden. Der entsprechende Fehler sagt mir, stattdessen Markertypen zu verwenden, aber the documention bietet auch keine Möglichkeit, mein Problem zu lösen.


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

Bitte versuchen Sie einen [MCVE] bereitzustellen, idealerweise als Link zum Spielplatz. Es ist viel einfacher, die Korrektheit einer Lösung zu testen, wenn man bei dem, was weggelassen wird, nicht raten muss. –

+1

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

+3

Nein, Ihr Code ist nicht vollständig sicher, auch mit einem einzigen Thread. Es ist UB, ein & T zu einem & mut T zu werfen.Sie sollten UnsafeCell dafür verwenden, was auch Ihr Sync-Problem beheben sollte. – BurntSushi5

Antwort

4

Innen Veränderlichkeit in Rust erfordert die Verwendung von UnsafeCell als Hinweis an den Compiler, dass die normalen Regeln nicht gelten kann.

Ihre Struktur sollte daher schauen so:

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

Und dann wird die Umsetzung von add_element angepasst:

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))); 
     } 
    } 
} 

Die Verwendung von UnsafeCell macht change_mut völlig unnötig: Es ist der Zweck der UnsafeCell schließlich, um innere Veränderlichkeit zu ermöglichen. Beachten Sie, dass die Methode get einen rohen Zeiger zurückgibt, der ohne einen unsafe-Block nicht dereferenziert werden kann.


Da UnsafeCell nicht implementiert Sync, Foo<T> werden Sync entweder nicht umsetzen, und deshalb wird es unnötig negative Implementierungen oder jede Markierung zu verwenden.


Wenn Sie es nicht direkt verwenden, stehen die Chancen, die Abstraktionen Sie Gebrauch machen werden darauf gebaut. Es ist so speziell wie es sein könnte: Es ist ein lang-Item, wie es durch sein Attribut #[lang = "unsafe_cell"] angezeigt wird.