2016-07-15 105 views
8

ho qualche tipo non copiabile e una funzione che consuma e (forse) lo produce:Come posso riutilizzare una casella di cui ho rimosso il valore?

type Foo = Vec<u8>; 

fn quux(_: Foo) -> Option<Foo> { 
    Some(Vec::new()) 
} 

Consideriamo ora un tipo che è in qualche modo concettualmente molto simile a Box:

struct NotBox<T> { 
    contents: T 
} 

Possiamo scrivere una funzione che sposta temporaneamente il contenuto dello NotBox e rimette qualcosa prima di restituirlo:

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> { 
    let foo = notbox.contents; // now `notbox` is "empty" 
    match quux(foo) { 
     Some(new_foo) => { 
      notbox.contents = new_foo; // we put something back in 
      Some(notbox) 
     } 
     None => None 
    } 
} 

Voglio scrivere una funzione analoga che funziona con Box es ma il compilatore non piace:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> { 
    let foo = *abox; // now `abox` is "empty" 
    match quux(foo) { 
     Some(new_foo) => { 
      *abox = new_foo; // error: use of moved value: `abox` 
      Some(abox) 
     } 
     None => None 
    } 
} 

potevo tornare Some(Box::new(new_foo)) invece, ma che esegue l'allocazione inutile - ho già qualche ricordo a mia disposizione! È possibile evitarlo?

Vorrei anche per sbarazzarsi dei match dichiarazioni ma ancora una volta il compilatore non è soddisfatto (anche per la versione NotBox):

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> { 
    let foo = notbox.contents; 
    quux(foo).map(|new_foo| { 
     notbox.contents = new_foo; // error: capture of partially moved value: `notbox` 
     notbox 
    }) 
} 

E 'possibile ovviare a questo?

+3

Sembra che tu stia chiedendo più di o ne domanda qui. La partita contro la mappa sembra essere spostata su una nuova domanda. –

+0

Stavo per rispondere alla prima parte, ma mi sono reso conto che non ho ancora capito come si possa uscire da 'Box '; non sembra essere correlato ai tratti 'Deref' o' DerefMut'. Quindi non vedo l'ora di dare una buona risposta! –

+0

@ChrisEmerson La parte della partita è stata qualcosa che è saltato fuori quando stavo cercando di creare l'esempio minimale del mio problema, quindi non ho fatto molte ricerche su di esso. Ho solo pensato che questo è probabilmente legato alla domanda generale e al fatto che non capisco come funzionano le mosse "parziali", quindi l'ho lasciato qui. – mrhania

risposta

8

Quindi, uscire da un Box è un caso speciale ... e adesso?

Il modulo presenta una serie di funzioni sicure per spostare i valori intorno, senza creare fori (!) Nella memoria di sicurezza di Rust.Di interesse qui sono swap e replace:

pub fn replace<T>(dest: &mut T, src: T) -> T 

che possiamo usare in questo modo:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> { 
    let foo = std::mem::replace(&mut *abox, Foo::default()); 

    match quux(foo) { 
     Some(new_foo) => { 
      *abox = new_foo; 
      Some(abox) 
     } 
     None => None 
    } 
} 

Aiuta anche nel caso map, perché non prendere in prestito il Box:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> { 
    let foo = std::mem::replace(&mut *abox, Foo::default()); 

    quux(foo).map(|new_foo| { *abox = new_foo; abox }) 
} 
+2

Lo svantaggio è che 'Foo: default()' deve esistere e si spera che sia economico da eseguire. Se nessuno dei due è vero, allora il suggerimento di passare a un 'Box >' sarebbe utile in quanto è possibile chiamare 'take' e' None' è l'impostazione predefinita di creazione di budget. – Shepmaster

+0

@Shepmaster: d'accordo :) Tuttavia preferisco attenermi a Scenario presentato e elaborato solo se il PO ha preoccupazioni più specifiche, altrimenti temo che la risposta possa annegare chiunque provi per leggerlo. –

4

Lo spostamento delle caselle è speciale nel compilatore. Puoi spostare qualcosa da loro, ma non puoi spostare qualcosa indietro, perché anche l'atto di uscire viene rilasciato. È possibile fare qualcosa di sciocco con std::ptr::write, std::ptr::read e std::ptr::replace, ma è difficile farlo bene, perché qualcosa di valido deve essere all'interno di uno Box quando viene rilasciato. Suggerirei semplicemente di accettare l'allocazione o passare a un valore Box<Option<Foo>>.

+1

Il mio più grande fastidio con Rust è che alcuni dettagli come questo apparentemente non sono documentati al di fuori dei thread di discussione o a volte nelle RFC. :-(FWIW, è menzionato [in questo RFC posticipato] (https://github.com/rust-lang/rfcs/pull/178). –

+2

@ChrisEmerson penso che in questo caso, l'involucro speciale di 'Box' è qualcosa che vuole essere rimosso e trasformato nella caratteristica "DerefMove" proposta. Non voglio mostrare troppo bucato sporco e farlo diventare widley internalizzato se può diventare più riutilizzabile e generico. Nota che [RFC è stato riavviato] (https://github.com/rust-lang/rfcs/pull/1646) – Shepmaster

0

Siamo in grado di scrivere una funzione che sposta temporaneamente fuori i contenuti del NotBox e mette qualcosa di nuovo in prima di tornare

Ecco perché si può parzialmente uscire dalla struct che si prende per valore. Si comporta come se tutti i campi fossero variabili separate. Ciò non è però possibile se la struct implementa Drop, perché drop richiede che l'intera struttura sia valida, sempre (in caso di panico).

Per quanto riguarda la soluzione alternativa, non sono state fornite informazioni sufficienti, in particolare, perché baz deve prendere come argomento Box e perché non è possibile utilizzare quux? Quali funzioni sono tue e quali fanno parte di un'API che non puoi modificare? Qual è il vero tipo di Foo? È grande?

La soluzione migliore sarebbe non utilizzare affatto Box.

Problemi correlati