2016-04-07 6 views
11

Sto utilizzando una chiave complessa per HashMap tale che la chiave comprende due parti e una parte è una String e non riesco a capire come eseguire le ricerche tramite il metodo HashMap::get senza allocare un nuovo String per ogni ricerca.Come evitare allocazioni temporanee quando si utilizza la chiave complessa per HashMap?

Ecco po 'di codice:

#[derive(Debug, Eq, Hash, PartialEq)] 
struct Complex { 
    n: i32, 
    s: String, 
} 

impl Complex { 
    fn new<S: Into<String>>(n: i32, s: S) -> Self { 
     Complex { 
      n: n, 
      s: s.into(), 
     } 
    } 
} 

fn main() { 
    let mut m = std::collections::HashMap::<Complex, i32>::new(); 
    m.insert(Complex::new(42, "foo"), 123); 

    // OK, but allocates temporary String 
    assert_eq!(123, *m.get(&Complex::new(42, "foo")).unwrap()); 
} 

Il problema è con l'affermazione finale. Passa, ma richiede un'allocazione dell'heap temporanea perché non riesco a creare un Complex senza costruire un String.

Per eliminare allocazioni temporanee come questa, Rust fornisce il tratto Borrow, che utilizza il metodo HashMap::get. Capisco come fare Borrow lavoro per semplici tasti, ad esempio, del Rust libreria standard PathBuf attrezzi Borrow<Path> facendo uso di std::mem::transmute sotto il cofano, ma non riesco a capire come farlo funzionare per il mio Complex Tipo:

#[derive(Debug)] 
struct Borrowable { 
    // ??? -- What goes here? Perhaps something like: 
    n: i32, 
    s1: &str, // ??? -- But what would the lifetime be? Or maybe: 
    s2: str, // ??? -- But how would I extend this to a complex type 
       //  containing two or more strings? 
} 

impl Borrowable { 
    fn new(n: i32, s: &str) -> &Self { 
     // ??? -- What goes here? It must not allocate. 
     unimplemented!(); 
    } 
} 

impl std::borrow::Borrow<Borrowable> for Complex { 
    fn borrow(&self) -> &Borrowable { 
     // ??? -- What goes here? How can I transmute a Complex into a 
     //  &Borrowable? 
     unimplemented!(); 
    } 
} 

Questo sembra un caso di uso comune e ho il sospetto che manchi qualcosa di importante su Borrow, ma sono a una perdita totale.

+0

Hai esaminato [Cow] (https://doc.rust-lang.org/std/borrow/enum.Cow.html)? – Aaronepower

risposta

5

Sembra che tu voglia questo.

Cow accetta uno &str o String.

use std::borrow::Cow; 

#[derive(Debug, Eq, Hash, PartialEq)] 
struct Complex<'a> { 
    n: i32, 
    s: Cow<'a, str>, 
} 

impl<'a> Complex<'a> { 
    fn new<S: Into<Cow<'a, str>>>(n: i32, s: S) -> Self { 
     Complex { 
      n: n, 
      s: s.into(), 
     } 
    } 
} 

fn main() { 
    let mut m = std::collections::HashMap::<Complex, i32>::new(); 
    m.insert(Complex::new(42, "foo"), 123); 

    assert_eq!(123, *m.get(&Complex::new(42, "foo")).unwrap()); 
} 

Un commento sui parametri di durata:

Se non ti piace il parametro a vita e avete solo bisogno di lavorare con &'static str o String quindi è possibile utilizzare Cow<'static, str> e rimuovere gli altri parametri a vita dal impl blocco e definizione della struttura.

+0

Ho bisogno di un giorno o due per digerirlo. È semplice, eppure mi fa schifo. La mia preoccupazione principale è che nel mio caso particolare 'Complex' è esposto nell'API della mia cassa, quindi devo assicurarmi di poter appesantire il tipo con un parametro di durata senza intaccare troppo la mia interfaccia. La mia prima reazione è che la funzionalità copy-on-write di 'Cow' è troppo pesante perché non sto mai copiando, ma in un certo senso lo sono quasi, perché a volte sto usando le istanze e a volte sto usando dei riferimenti . Dopo aver finito di digerire la tua risposta, ti farò sapere come funziona. –

+0

Potrebbe essere possibile eliminare i parametri di durata. Vedi la mia modifica. –

+0

Non riesco a eliminare il parametro lifetime perché a volte prendo in prestito da un 'str' non statico. Nondimeno, mi piace molto la tua idea perché semplificherebbe il codice altrove perché il nuovo 'Complesso' copre sia i casi di possedere che di prendere in prestito - non c'è bisogno di un tipo unico per ogni caso. Una cosa che mi intriga è che la Rust Standard Library non ha utilizzato la modalità "Cow" in invece di avere due tipi per coprire i casi di possedere e prendere in prestito per "Path"/"PathBuf" et al. L'idea di "Mucca" sembra più generalizzata perché funziona anche per tipi complessi. Questo è stato fatto per l'efficienza in fase di runtime? –