No hay una sintaxis literal de mapa en Rust. No sé la razón exacta , pero espero que el hecho de que haya múltiples estructuras de datos que actúan como un mapa (como ambos BTreeMap
y HashMap
) dificultaría la elección de una.
Sin embargo, puede crear una macro para que haga el trabajo por usted, como se demuestra en ¿Por qué esta macro HashMap de óxido ya no funciona? . Aquí está esa macro simplificada un poco y con suficiente estructura para que se pueda ejecutar en el patio de recreo :
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));
}
Esta macro evita la asignación de un intermedio innecesario Vec
, pero no se usa, HashMap::with_capacity
por lo que puede haber algunas reasignaciones inútiles de los HashMap
valores a medida que se agregan. Es posible una versión más complicada de la macro que cuenta los valores, pero los beneficios de rendimiento probablemente no sean algo de lo que la mayoría de los usos de la macro se beneficiarían.
En una versión nocturna de Rust, puede evitar tanto la asignación innecesaria (¡y la reasignación!) Como la necesidad de una macro:
#![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);
}
Esta lógica también se puede volver a envolver en una macro:
#![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);
}
Ver también:
grabbag_macros
caja. Puede ver la fuente aquí: github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/… .DictionaryLiteral
se puede usar para inicializar cualquier tipo que se ajuste aExpressibleByDictionaryLiteral
(aunque la biblioteca estándar ofrece uno de esos tiposDictionary
)Recomiendo la caja maplit .
Para citar de la documentación:
use maplit::hashmap; let map = hashmap!{ "a" => 1, "b" => 2, };
fuente
Hay un ejemplo de cómo lograr esto en la documentación para
HashMap
:let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)] .iter() .cloned() .collect();
fuente
String
lugar de&str
.vec![ (name, value) ].into_iter().collect()
Como señaló @Johannes en los comentarios, es posible usarlo
vec![]
porque:Vec<T>
implementa elIntoIterator<T>
rasgoHashMap<K, V>
implementosFromIterator<Item = (K, V)>
lo que significa que puedes hacer esto:
let map: HashMap<String, String> = vec![("key".to_string(), "value".to_string())] .into_iter() .collect();
Puede usar,
&str
pero es posible que deba anotar vidas útiles si no lo es'static
:let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();
fuente
Puede utilizar la
velcro
caja *. Esto es similar amaplit
, como se recomienda en otras respuestas, pero con más tipos de colección, mejor sintaxis (¡al menos en mi opinión!) Y más funciones.Suponiendo que desea usar
String
s en lugar de&str
, su ejemplo exacto se vería así: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!{} } } } };
Eso es un poco feo por cómo
String
se construyen los s. Pero se puede hacer un poco más limpio, sin cambiar el tipo de clave, usandohash_map_from!
que automáticamente hará conversiones: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!{} } } } };
Que no es mucho más detallado que la versión Go.
* Divulgación completa: soy el autor de esta caja.
fuente
Por un elemento
Si desea inicializar el mapa con un solo elemento en una línea (y sin una mutación visible en su código), puede hacer:
let map: HashMap<&'static str, u32> = Some(("answer", 42)).into_iter().collect();
Esto es gracias a la utilidad de
Option
poder convertirse en unIterator
usuariointo_iter()
.En código real, probablemente no necesite ayudar al compilador con el tipo:
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); }
También hay una manera de encadenar esto para tener más de un elemento haciendo algo así
Some(a).into_iter().chain(Some(b).into_iter()).collect()
, pero esto es más largo, menos legible y probablemente tenga algunos problemas de optimización, así que lo desaconsejo.fuente
He visto un montón de soluciones sofisticadas, pero solo quería algo simple. Con ese fin, aquí hay un rasgo:
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) }
Creo que es una buena opción, ya que es esencialmente lo que ya utilizan Ruby y Nim:
fuente
copied()
no permitir el tipo que no implementa la copia, evitando la clonación por nada, o al menos hacerlo explícito usando un cloned_to_map () si también desea poder hacerlo con solo el tipo clonado.