2016-04-07 12 views
11

Używam złożonego klucza dla HashMap w taki sposób, że klucz składa się z dwóch części, a jedna część to String i nie mogę wymyślić sposobu wyszukiwania za pomocą metody HashMap::get bez przydzielania nowego String dla każdego wyszukiwania.Jak uniknąć tymczasowych alokacji przy użyciu złożonego klucza dla HashMap?

Oto niektóre kodu:

#[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()); 
} 

Problem jest z ostatnim twierdzeniem. Przepuszcza, ale wymaga tymczasowej alokacji sterty, ponieważ nie mogę skonstruować obiektu Complex bez tworzenia obiektu String.

Aby wyeliminować tymczasowe alokacje w ten sposób, Rust udostępnia cechę Borrow, z której korzysta metoda HashMap::get. Rozumiem, jak zrobić Borrow prac prostych klawiszy, na przykład, rdza biblioteki standardowej za PathBuf narzędzi Borrow<Path> poprzez wykorzystanie std::mem::transmute pod maską, ale nie mogę dowiedzieć się, jak sprawić, by pracować dla mojego Complex typu:

#[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!(); 
    } 
} 

Wydaje się, że jest to typowy przypadek użycia i podejrzewam, że brakuje mi czegoś ważnego o Borrow, ale jestem w całkowitej stracie.

+0

Zajrzałeś do [Cow] (https://doc.rust-lang.org/std/borrow/enum.Cow.html)? – Aaronepower

Odpowiedz

5

Wygląda na to, że tego chcesz.

Cow zaakceptuje &str lub 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()); 
} 

Komentarz o parametrach Żywotność:

Jeśli nie podoba parametr dożywotnią i trzeba tylko pracować z &'static str lub String następnie można użyć Cow<'static, str> i usunąć pozostałe parametry życiowe z IMPL definicja bloku i struktury.

+0

Będę potrzebował dnia lub dwóch, żeby to przetrawić. To proste, ale to dmucha w moim umyśle. Moim głównym zmartwieniem jest to, że w moim konkretnym przypadku "Complex" jest ujawniony w API mojej skrzynki, więc muszę się upewnić, że mogę obciążyć ten typ parametrem życia bez zbytniego zamazywania mojego interfejsu. Moja początkowa reakcja jest taka, że ​​funkcja "kopiuj-napisz" jest zbyt ciężka, ponieważ nigdy nie robię żadnego kopiowania, ale w pewnym sensie prawie jestem, ponieważ czasami używam instancji i czasami używam referencji . Gdy skończę trawić twoją odpowiedź, dam ci znać, jak to działa. –

+0

Możesz być w stanie pozbyć się parametrów życia. Zobacz moją edycję. –

+0

Nie można wyeliminować parametru czasu życia, ponieważ czasami pożyczam od niestatycznego 'str'. Niemniej jednak bardzo podoba mi się twój pomysł, ponieważ uprościłoby to kod w innym miejscu, ponieważ nowy "Complex" obejmuje zarówno sprawy posiadania, jak i wypożyczania - nie ma potrzeby stosowania unikalnego typu dla każdego przypadku. Intryguje mnie jedno, że Standardowa Biblioteka Rdzy nie poszła na ścieżkę używania "Krowy" zamiast dwóch typów, aby pokryć przypadki posiadania i pożyczania dla 'Path' /' PathBuf' i innych. Pomysł "Krowia" wydaje się bardziej uogólniony, ponieważ działa również w przypadku typów złożonych. Czy było to zrobione dla wydajności w czasie wykonywania? –

Powiązane problemy