W Rust nie ma składni literału mapy. Nie znam dokładnego powodu, ale spodziewam się, że fakt, że istnieje wiele struktur danych, które działają jak mapy (takie jak oba BTreeMap
i HashMap
), utrudniłby wybranie jednej.
Możesz jednak utworzyć makro, które wykona zadanie za Ciebie, jak pokazano w artykule Dlaczego to rdzawe makro HashMap już nie działa? . Oto makro nieco uproszczone i posiadające wystarczającą strukturę, aby można było je uruchomić na placu zabaw :
macro_rules! map(
{ $($key:expr => $value:expr),+ } => {
{
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key, $value);
)+
m
}
};
);
fn main() {
let names = map!{ 1 => "one", 2 => "two" };
println!("{} -> {:?}", 1, names.get(&1));
println!("{} -> {:?}", 10, names.get(&10));
}
To makro pozwala uniknąć przydzielania niepotrzebnego elementu pośredniego Vec
, ale nie używa, HashMap::with_capacity
więc mogą wystąpić niepotrzebne realokacje w HashMap
miarę dodawania wartości. Bardziej skomplikowana wersja makra zliczającego wartości jest możliwa, ale korzyści w zakresie wydajności prawdopodobnie nie są czymś, na czym większość zastosowań tego makra skorzystałaby.
W nocnej wersji Rusta można uniknąć zarówno niepotrzebnej alokacji (i realokacji!), Jak i konieczności stosowania makra:
#![feature(array_value_iter)]
use std::array::IntoIter;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;
fn main() {
let s = Vec::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);
let s = BTreeSet::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);
let s = HashSet::<_>::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);
let s = BTreeMap::from_iter(IntoIter::new([(1, 2), (3, 4)]));
println!("{:?}", s);
let s = HashMap::<_, _>::from_iter(IntoIter::new([(1, 2), (3, 4)]));
println!("{:?}", s);
}
Ta logika może być następnie zawinięta z powrotem w makro:
#![feature(array_value_iter)]
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
macro_rules! collection {
($($k:expr => $v:expr),* $(,)?) => {
std::iter::Iterator::collect(std::array::IntoIter::new([$(($k, $v),)*]))
};
($($v:expr),* $(,)?) => {
std::iter::Iterator::collect(std::array::IntoIter::new([$($v,)*]))
};
}
fn main() {
let s: Vec<_> = collection![1, 2, 3];
println!("{:?}", s);
let s: BTreeSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);
let s: HashSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);
let s: BTreeMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);
let s: HashMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);
}
Zobacz też:
grabbag_macros
skrzyni. Możesz zobaczyć źródło tutaj: github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/… .DictionaryLiteral
może być użyty do zainicjowania dowolnego typu, który jest zgodny zExpressibleByDictionaryLiteral
(nawet jeśli standardowa biblioteka oferuje jeden taki typDictionary
)Polecam skrzynkę podświetlaną na mapę .
Cytat z dokumentacji:
use maplit::hashmap; let map = hashmap!{ "a" => 1, "b" => 2, };
źródło
Przykład, jak to osiągnąć, znajduje się w dokumentacji
HashMap
:let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)] .iter() .cloned() .collect();
źródło
String
zamiast&str
.vec![ (name, value) ].into_iter().collect()
Jak zauważył @Johannes w komentarzach, można go używać,
vec![]
ponieważ:Vec<T>
realizujeIntoIterator<T>
cechęHashMap<K, V>
przyboryFromIterator<Item = (K, V)>
co oznacza, że możesz to zrobić:
let map: HashMap<String, String> = vec![("key".to_string(), "value".to_string())] .into_iter() .collect();
Możesz użyć,
&str
ale może być konieczne dodanie adnotacji, jeśli nie jest'static
:let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();
źródło
Możesz skorzystać ze
velcro
skrzynki *. Jest podobny do tegomaplit
, co jest zalecane w innych odpowiedziach, ale z większą liczbą typów kolekcji, lepszą składnią (przynajmniej moim zdaniem!) I większą liczbą funkcji.Zakładając, że chcesz użyć
String
s zamiast&str
, dokładny przykład wyglądałby tak:use std::collections::HashMap; use velcro::hash_map; struct Node { name: String children: HashMap<String, Node>, } let map = hash_map! { String::from("element0"): Node { name: "My New Element".into(), children: hash_map! { String::from("child0"): Node { name: "child0".into(), children: hash_map!{} } } } };
To trochę brzydkie ze względu na to, jak
String
są zbudowane. Ale można to uczynić nieco czystszym, bez zmiany typu klucza, używając,hash_map_from!
które automatycznie wykona konwersje:use std::collections::HashMap; use velcro::{hash_map, hash_map_from}; let map: HashMap<String, Node> = hash_map_from! { "element0": Node { name: "My New Element".into(), children: hash_map_from! { "child0": Node { name: "child0".into(), children: hash_map!{} } } } };
Co nie jest dużo bardziej szczegółowe niż wersja Go.
* Pełne ujawnienie: jestem autorem tej skrzynki.
źródło
Na jeden element
Jeśli chcesz zainicjować mapę tylko z jednym elementem w jednej linii (i bez widocznej mutacji w kodzie), możesz zrobić:
let map: HashMap<&'static str, u32> = Some(("answer", 42)).into_iter().collect();
Jest to możliwe dzięki przydatności
Option
jest w stanie stać sięIterator
używającinto_iter()
.W prawdziwym kodzie prawdopodobnie nie musisz pomagać kompilatorowi z typem:
use std::collections::HashMap; fn john_wick() -> HashMap<&'static str, u32> { Some(("answer", 42)).into_iter().collect() } fn main() { let result = john_wick(); let mut expected = HashMap::new(); expected.insert("answer", 42); assert_eq!(result, expected); }
Istnieje również sposób na połączenie tego w łańcuch, aby więcej niż jeden element robił coś podobnego
Some(a).into_iter().chain(Some(b).into_iter()).collect()
, ale jest dłuższy, mniej czytelny i prawdopodobnie ma pewne problemy z optymalizacją, więc odradzam to.źródło
Widziałem kilka wymyślnych rozwiązań, ale chciałem tylko czegoś prostego. W tym celu oto cecha:
use std::collections::HashMap; trait Hash { fn to_map(&self) -> HashMap<&str, u16>; } impl Hash for [(&str, u16)] { fn to_map(&self) -> HashMap<&str, u16> { self.iter().cloned().collect() } } fn main() { let m = [("year", 2019), ("month", 12)].to_map(); println!("{:?}", m) }
Myślę, że to dobra opcja, ponieważ zasadniczo jest to coś, czego używają już Ruby i Nim:
źródło
copied()
, aby uniemożliwić typu, które nie wdrażają kopię, unikając klonowanie za nic, albo przynajmniej uczynić go jednoznacznie za pomocą cloned_to_map (), jeśli chcesz, aby móc to zrobić tylko ze sklonowanego typu.