2015-09-28 19 views
5

Ho scritto del codice. Funziona ... ma è sicuro?Sicurezza di trasmettere tipi arbitrari da utilizzare

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

Come potete vedere, scrivo un Copy valore arbitrario di piccola taglia abbastanza in un usize, e la nave intorno in un Atomic. Allora lo leggo come un nuovo valore.

In sostanza utilizzo lo usize come blocco di memoria di dimensioni size_of::<usize>().

Se questo è sicuro, il passaggio successivo è considerare le operazioni più elaborate.

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

Queste non sono ovviamente sempre particolarmente sensibile overflow (poiché due valori "uguali" potrebbero confrontare disuguale causa bit al di fuori del T), ma ancora sembrano ben definiti ... Credo.

Quindi, questo è sicuro e perché?

+0

Ti riferisci alla [definizione di sicurezza di Rust] (http://doc.rust-lang.org/reference.html#behavior-not-considered-unsafe) (anche rilevante, il [comportamento non definito] (http : //doc.rust-lang.org/reference.html#behavior-considered-undefined))? O intendi un tipo più generale di "sicurezza"? – Shepmaster

+0

La definizione di ruggine, per lo più. Commenti su problemi particolarmente sorprendenti ma tecnicamente sicuri sarebbero comunque una bella aggiunta. – Veedrac

risposta

2

È quasi sicuro ... ma non del tutto. Probabilmente stai pensando solo alle persone che usano Atomic con numeri interi e float, ma anche i riferimenti sono Copy. Un utente potrebbe facilmente causare un arresto anomalo utilizzando carichi rilassati e archivi su un Atomic<&&u32>.

Su una nota a margine, il tuo fetch_add e fetch_sub non funzionerà correttamente su sistemi big-endian.

+1

* su sistemi big-endian * - Sarebbe bello se esistesse un modo per far sì che gli ecosistemi CI (come TravisCI) avessero modo di testare {x86, ARM, ...} e {big, little} -endian sistemi in aggiunta al supporto di {32, 64} bit che conosco. – Shepmaster

+0

Buona cattura! Usando 'struct Atomic <'a, T: Copy +' a> (AtomicUsize, PhantomData <(& 'a mut T, T)>);' sembra risolvere il primo problema. :) – Veedrac

Problemi correlati