2016-01-20 4 views
7

Il seguente codice viene compilato senza preavviso su ogni notte 1.7.0:Lieve tweak per il codice personalizzato FnBox impedisce di compilazione

trait FnBox { 
    fn call_box(self: Box<Self>); 
} 

impl <F: FnOnce()> FnBox for F { 
    fn call_box(self: Box<F>) { 
     (*self)() 
    } 
} 

fn main() {} 

Ma quando faccio questa piccola modifica, che ho pensato che dire la stessa identica cosa, io ottenere un errore su FnOnce non bloccato e non spostabile.

trait FnBox { 
    fn call_box(self: Box<Self>); 
} 

impl FnBox for FnOnce() { 
    fn call_box(self: Box<FnOnce()>) { 
     (*self)(); 
    } 
} 

fn main() {} 

Messaggio di errore:

practice.rs:7:10: 7:15 error: cannot move a value of type core::ops::FnOnce() + 'static: the size of core::ops::FnOnce() + 'static cannot be statically determined [E0161] 
practice.rs:7   (*self)(); 
         ^~~~~ 
practice.rs:7:10: 7:15 help: run `rustc --explain E0161` to see a detailed explanation 
error: aborting due to previous error 

Qual è la differenza tra questi due esempi, e perciò non ci sono problemi con il primo?

risposta

9

C'è una grande differenza, in realtà. Nel primo pezzo di codice:

impl<F: FnOnce()> FnBox for F { 
    fn call_box(self: Box<F>) { 
     (*self)() 
    } 
} 

si dichiara che per qualsiasi tipo F che implementa FnOnce attuiamo FnBox. F è un tipo concreto e in ciascun sito di chiamata il metodo call_box() verrà monomorfizzato. Il tipo di calcestruzzo di F in ogni sito di chiamata, nonché le sue dimensioni, è noto al compilatore, quindi non ci sono problemi con questa definizione.

Nella seconda parte del codice, tuttavia:

impl FnBox for FnOnce() { 
    fn call_box(self: Box<FnOnce()>) { 
     (*self)(); 
    } 
} 

si precisa che nudo oggetto tratto tipo implementa FnBox. Tuttavia, questa implementazione non è corretta: mentre Box<FnOnce()> è un tipo corretto, dimensionato che è adatto per variabili e argomenti di funzione, FnOnce() di per sé non è - è un tipo di oggetto tratto nudo e non è sicuro, ovvero la sua dimensione non è nota al compilatore. Ciò pone diverse restrizioni su cosa è possibile fare con questo tipo e una delle principali restrizioni è che non è possibile utilizzare valori di questo tipo in base al valore. Tuttavia, questo è esattamente ciò che accade in questo codice: si tenta di dereferenziare Box<FnOnce()> per ottenere FnOnce().

Precedentemente in base al valore self i metodi significherebbe che il carattere è not object-safe e poiché FnOnce::call_once utilizza l'istanza di implementazione in base al valore, non sarebbe sicuro per gli oggetti. Tuttavia, i metodi a valore self non rendono l'oggetto tratto non sicuro dal momento in cui è stato implementato RFC 817. Tuttavia, i metodi a valore nominale self non possono essere richiamati su un oggetto tratto in base al ragionamento precedente.

+0

buona risposta. Per ulteriori letture faremo anche riferimento al capitolo del Libro su [oggetti tratti] (https://doc.rust-lang.org/book/trait-objects.html) e alla [discussione su FnBox] (https://github.com/rust-lang/rust/issues/28796) –

+0

'FnOnce' è sicuro per gli oggetti, è solo che il suo metodo non può essere chiamato attraverso un oggetto. * sicurezza oggetto * è equivalente a * tratto ha un tipo di oggetto *. Senza 'FnOnce',' FnMut' non potrebbe essere anche sicuro per gli oggetti. – bluss

+0

@bluss, secondo la pagina della documentazione un tratto è sicuro per gli oggetti se non richiede 'Self: Sized' (' FnOnce' no) e se tutti i suoi metodi sono object-safe. Un metodo è object-safe se richiede o 'Self: Sized' (' call_once() 'no) o se non è nemmeno generico (' call_once() 'non è generico, quindi va bene) né usa' Self'. Quest'ultimo è il problema, perché 'call_once()' usa 'Self' sia implicitamente, come un tipo di' self', che esplicitamente, in 'Self :: Output', quindi' call_once() 'non è object-safe. Pertanto, 'FnOnce' non è sicuro anche per gli oggetti. –

Problemi correlati