2015-09-28 8 views
5

Ich schrieb etwas Code. Es funktioniert ... aber ist es sicher?Sicherheit des Gießens beliebiger Typen zu verwenden

use std::mem; 
use std::ptr; 
use std::marker::PhantomData; 

struct Atomic<T: Copy>(AtomicUsize, PhantomData<T>); 

impl<T: Copy> Atomic<T> { 
    unsafe fn encode(src: T) -> usize { 
     assert!(mem::size_of::<T>() <= mem::size_of::<usize>()); 

     let mut dst = 0; 
     ptr::write(&mut dst as *mut usize as *mut T, src); 
     dst 
    } 

    unsafe fn decode(src: usize) -> T { 
     assert!(mem::size_of::<T>() <= mem::size_of::<usize>()); 
     ptr::read(&src as *const usize as *const T) 
    } 

    fn new(val: T) -> Atomic<T> { 
     unsafe { 
      Atomic(AtomicUsize::new(Self::encode(val)), PhantomData) 
     } 
    } 

    fn load(&self, order: Ordering) -> T { 
     unsafe { Self::decode(self.0.load(order)) } 
    } 

    fn store(&self, val: T, order: Ordering) { 
     unsafe { self.0.store(Self::encode(val), order) } 
    } 
} 

impl<T: Copy + Default> Default for Atomic<T> { 
    fn default() -> Atomic<T> { 
     Self::new(T::default()) 
    } 
} 

Wie man sehen kann, schreibe ich einen beliebigen Copy Wert klein genug, um eine Größe in ein usize, und es um in einem Atomic versenden. Ich lese es dann als neuen Wert vor.

Im Wesentlichen verwende ich die usize als Speicherblock der Größe size_of::<usize>().

Wenn dies sicher ist, ist der nächste Schritt, einen schickeren Betrieb in Betracht zu ziehen.

unsafe trait PackedInt {} 
unsafe impl PackedInt for u8 {} 
unsafe impl PackedInt for i8 {} 
unsafe impl PackedInt for u32 {} 
unsafe impl PackedInt for i32 {} 
unsafe impl PackedInt for u64 {} 
unsafe impl PackedInt for i64 {} 

impl<T: Copy + PackedInt> Atomic<T> { 
    fn compare_and_swap(&self, current: T, new: T, order: Ordering) -> T { 
     unsafe { 
      Self::decode(self.0.compare_and_swap(
       Self::encode(current), 
       Self::encode(new), 
       order 
      )) 
     } 
    } 

    fn fetch_add(&self, val: T, order: Ordering) -> T { 
     unsafe { 
      Self::decode(self.0.fetch_add(Self::encode(val), order)) 
     } 
    } 

    fn fetch_sub(&self, val: T, order: Ordering) -> T { 
     unsafe { 
      Self::decode(self.0.fetch_sub(Self::encode(val), order)) 
     } 
    } 
} 

Diese Überlauf natürlich nicht immer besonders sinnvoll sind (da zwei „gleich“ Werte aufgrund von Bits außerhalb des T ungleich vergleichen konnte), aber sie scheinen immer noch gut definierte ... denke ich.

Also, ist das sicher und warum?

+0

Beziehen Sie sich auf die [Rust Definition von Sicherheit] (http://doc.rust-lang.org/reference.html#behavior-not-considered-unsafe) (auch relevant, das [undefinierte Verhalten] (http : //doc.rust-lang.org/reference.html#behavior-consided-undefined))? Oder meinst du eine allgemeinere Art von "sicher"? – Shepmaster

+0

Die Rust-Definition, meistens. Kommentare zu besonders überraschenden aber technisch sicheren Problemen wären eine gute Ergänzung. – Veedrac

Antwort

2

Es ist fast sicher ... aber nicht ganz. Sie denken wahrscheinlich nur an Leute, die Atomic mit Ganzzahlen und Gleitkommazahlen verwenden, aber Referenzen sind auch Copy. Ein Benutzer könnte leicht einen Absturz mit entspannten Lasten verursachen und speichert auf einem Atomic<&&u32>.

Nebenbei funktioniert Ihre fetch_add und fetch_sub nicht korrekt auf Big-Endian-Systemen.

+1

* auf Big-Endian-Systemen * - es wäre cool, wenn die CI-Ökosysteme (wie TravisCI) Möglichkeiten hätten, {x86, ARM, ...} und {big, little} -endian zu testen Systeme zusätzlich zu der {32, 64} -Bit-Unterstützung, die ich kenne. – Shepmaster

+0

Guter Fang! Mit 'struct Atomic <'a, T: Kopieren +' a> (AtomicUsize, PhantomData <(& 'a mut T, T)>); scheint das erste Problem zu beheben. :) – Veedrac