-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add hashmap, hashset, treemap, and treeset macros #542
Comments
For my own part, Also, I would vote for just getting something ergonomic in for 1.0 and think about optimisations later. hashmap!("foo": 5, "bar": 8) Just something that extracts to {
a = Hashmap::new();
a.insert("foo", 5);
a.insert("bar", 8);
a
} ...would be fine. |
My preference is For example: hashmap! {"foo": 42, "bar": 64} This is similar to JSON and Python, and has the same "theme" as The JSON similarity makes it easier to directly copy a JSON snippet into a Rust code. And |
I believe |
Yeah we should really use |
So is there any movement? |
@aspcartman Not really - a more general abstraction is needed for std, but you could easily land these in a cargo crate. |
Very sorry. I am new to rust. I wanted to create a HashSet with some default values. I found the vec->to_iter->collect pattern and thought it could be nicer. Then i found this thread. Are macros favored over functions? I would love to see a function on structures like new that took a default object to convert from. Is there something like this? Since you cant overload a method maybe it can use trait bounds? And each struct in the std can define its conversion for each other. If someone wants to have a new conversion they define it for their object? |
Macros are never favoured, but that's just the fact if you can solve it with a function, you should. Macros may be the only way to provide this functionality with the features we want, for example avoiding any intermediate extra allocation. |
Thanks @bluss . I now understand that using a function with trait bounds requires a second object to convert from where macros do not. I dont understand macros well enough. At some point they turn in to real code. Can I view what that code is doing? In this example http://rustbyexample.com/macros/repeat.html how does $y store all the rest of the arguments? Can I write a function that does the same thing? I haven't found an example or documentation for it. thanks |
Sure, you can use
|
I published a crate maplit on crates.io with simple type-specific The Prior art in this business include generic container literals in literator and generic container literals in grabbag_macros and an older crate with generic container literals called construct. The generic solutions all have some drawbacks, so I think it's nice to have the type-specific macros out there as well. |
Personally, I would use brackets (since braces are apparently reserved for other things, as Gankro said). Swift uses brackets for array and mapping literals, and I'm sure it's not the only one. Also, I wonder if this could be generalized to support all |
This is something the user, not implementor, decides. For example all these work and are equivalent: let a = vec!{1, 3, 5};
let b = vec![1, 3, 5];
let c = vec!(1, 3, 5); |
@nagisa, right but there should be a convention. |
This changes required a couple smaller changes: * Base nodes now indicate key column * Record enum can now be `DeleteRequest` * Streamers send `Vec<StreamUpdate>`'s instead of Records
Is there any movement on this? Because it sounds really nice to have. I agree with the using the |
It would be nice if this was in the standard library. Could we get |
@JelteF Unfortunately +1 I believe this should be in the std library as well. |
Hi, I believe this should be in the std library for a couple of reasons:
(You can see this story for more informations. It's a fake, but the point still stands. |
If there are different types of maps, So that it becomes let map: HashMap = map!{"key" => "value"}; |
@stevenroose You can always try it out with different operations on the playground... One less great aspect of doing it that way is that you are sort-of hoping that the right operations exist on the map type... If we had some way of being polymorphic over maps in the type system (we need to be able to quantify over |
I'd suggest |
What do you think of the following approach? It seems quite pleasant to me. Recall that #![allow(unused_macros)]
#![allow(unused_imports)]
use std::collections::HashMap;
use std::collections::BTreeMap;
trait MapLiteral<K,V> {
fn new() -> Self;
fn with_capacity(n: usize) -> Self;
fn insert(m: &mut Self, k: K, v: V);
}
impl<K,V> MapLiteral<K,V> for HashMap<K,V>
where K: std::cmp::Eq+std::hash::Hash
{
fn new() -> Self {HashMap::new()}
fn with_capacity(n: usize) -> Self {HashMap::with_capacity(n)}
fn insert(m: &mut Self, k: K, v: V){m.insert(k,v);}
}
impl<K,V> MapLiteral<K,V> for BTreeMap<K,V>
where K: std::cmp::Ord
{
fn new() -> Self {BTreeMap::new()}
fn with_capacity(_n: usize) -> Self {BTreeMap::new()}
fn insert(m: &mut Self, k: K, v: V){m.insert(k,v);}
}
// replace ,* by ,* $(,)?
macro_rules! map {
($( $key:tt: $value:expr ),*) => {{
let mut _temp_map = MapLiteral::new();
$(MapLiteral::insert(&mut _temp_map,$key.into(),$value.into());)*
_temp_map
}};
({$init:expr} {$( $key:tt: $value:expr ),*}) => {{
let mut _temp_map = $init;
$(MapLiteral::insert(&mut _temp_map, $key.into(),$value.into());)*
_temp_map
}};
({$init:expr; $tk:expr, $tv:expr} {$( $key:tt: $value:expr ),*}) => {{
let mut _temp_map = $init;
$(MapLiteral::insert(&mut _temp_map,$tk($key),$tv($value));)*
_temp_map
}};
({$tk:expr, $tv:expr} {$( $key:tt: $value:expr ),*}) => {{
let mut _temp_map = MapLiteral::new();
$(MapLiteral::insert(&mut _temp_map,$tk($key),$tv($value));)*
_temp_map
}};
} Let's try it out. fn main() {
let m: HashMap<i32,i32> = map!{};
println!("{:?}",m);
let m: HashMap<i32,i32> = map!{1: 2, 2: 1};
println!("{:?}",m);
let m: HashMap<Box<str>,i32> = map!{"x": 1, "y": 2};
println!("{:?}",m);
let m: HashMap<&str,i32> = map!{"x": 1, "y": 2};
println!("{:?}",m);
let m: HashMap<String,i32> = map!{"x": 1, "y": 2};
println!("{:?}",m);
let m: HashMap<Box<str>,i32> = map!{"x": 1, "y": 2};
println!("{:?}",m);
let m: HashMap<i32,Option<i32>> = map!{1: 2, 2: None};
println!("{:?}",m);
let m: HashMap<(i32,i32),i32> = map!{(1,1): 1, (1,2): 2};
println!("{:?}",m);
let m: HashMap<[i32;2],i32> = map!{[1,1]: 1, [1,2]: 2};
println!("{:?}",m);
let m: HashMap<Vec<i32>,i32> = map!{
(vec![1,2]): 1,
(vec![1,2,3]): 2
};
println!("{:?}",m);
let m: BTreeMap<String,HashMap<i32,i32>> = map!{
"x": HashMap::from(map!{1: 1, 2: 2}),
"y": HashMap::from(map!{1: 2, 2: 1})
};
println!("{:?}",m);
let m = map!{{HashMap::<i32,i32>::with_capacity(100)}{
1: 2, 2: 1
}};
println!("{:?}",m);
let m: HashMap<Box<String>,i32> = map!{{
|x| Box::new(String::from(x)), |x| x
}{
"x": 1, "y": 2
}};
println!("{:?}",m);
let m: HashMap<String,Box<[i32]>> = map!{{
String::from, |x| Box::new(x) as Box<[i32]>
}{
"x": [1,1], "y": [1,2]
}};
println!("{:?}",m);
let m: HashMap<String,Box<dyn std::any::Any>> = map!{{
String::from, |x| Box::new(x) as Box<dyn std::any::Any>
}{
"x": 1, "y": "a", "z": [1,2]
}};
println!("{:?}",m);
} |
How is that any better than |
Well, the |
I see. In that case, maybe |
But clones could be more expensive to do and not all types support clones, so that doesnt work. We could use |
I think that macros for hash collections should definitely be in std. |
What exactly is blocking progress on this? Are people still conflicted as to whether this should be in the standard library? I think consistency with |
The only thing blocking this issue is someone creating an RFC. As far as I'm concerned, maplit already has appropriate definitions and could be pulled into the standard library, although there are some outstanding issues that might be worth resolving first. |
Since it's a simple library addition, one could just file a rust-lang/rust PR directly. |
We've discussed doing this with either In principle, If otoh you attempt this with HashMap then The above approaches are not really optimal because they do not require allocation, but depend upon
Ain't clear core needs |
I think there may be enough design decisions open for discussion to warrant an RFC, but one could always submit a pull request and see what the libs team thinks. |
This comment has been minimized.
This comment has been minimized.
It's possible to write a version that's applicable to multiple types of collections using the by-value iterator for arrays: macro_rules! collection {
// map-like
($($k:expr => $v:expr),* $(,)?) => {{
use std::iter::{Iterator, IntoIterator};
Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]))
}};
// set-like
($($v:expr),* $(,)?) => {{
use std::iter::{Iterator, IntoIterator};
Iterator::collect(IntoIterator::into_iter([$($v,)*]))
}};
}
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
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);
} Previous versionThis uses the (currently unstable) by-value iterator for arrays and #![feature(array_value_iter)]
macro_rules! seq {
// Sequences
($($v:expr,)*) => {
std::array::IntoIter::new([$($v,)*]).collect()
};
($($v:expr),*) => {
std::array::IntoIter::new([$($v,)*]).collect()
};
// Maps
($($k:expr => $v:expr,)*) => {
std::array::IntoIter::new([$(($k, $v),)*]).collect()
};
($($k:expr => $v:expr),*) => {
std::array::IntoIter::new([$(($k, $v),)*]).collect()
};
}
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
fn main() {
let s: Vec<_> = seq!(1, 2, 3);
println!("{:?}", s);
let s: BTreeSet<_> = seq!(1, 2, 3);
println!("{:?}", s);
let s: HashSet<_> = seq!(1, 2, 3);
println!("{:?}", s);
let s: BTreeMap<_, _> = seq!(1 => 2, 3 => 4);
println!("{:?}", s);
let s: HashMap<_, _> = seq!(1 => 2, 3 => 4);
println!("{:?}", s);
} See also the macro-free version. |
@shepmaster: this is very neat. I'd be in favour of a generic initialisation macro like this if it was demonstrated to be efficient. |
@shepmaster This is a better, general and extendable macro, and which does not rely on a new trait to be created. +1 |
That seems really cool, yeah. However, does it really make sense to this of Also like others wrote before, I think we should consider using |
@jplatte unfortunately we can't use |
@kennytm what would the possible workarounds for that be? IMHO requiring parentheses for type ascription in that position would be the best solution but I understand that that's probably not that easy to implement, right? |
let hash_map: HashMap<Key, Value> = map!("a" => 1, "b" => 2, "c" => 3);
// …is analogous to a function acting as a map…
let hash_map: impl Fn(Key) -> Value = |key| match key {
"a" => 1,
"b" => 2,
"c" => 3,
};
|
@jplatte If we implement We could also do it like macro_rules! map {
(@ A=[$($array:tt)*], K=[], R=[]) => {
std::array::IntoIter::new([$($array)*]).collect()
};
(@ A=[$($array:tt)*], K=[$($key:tt)+], R=[: $value:expr, $($rest:tt)*]) => {
map!(@ A=[$($array)* ($($key)+, $value),], K=[], R=[$($rest)*])
};
(@ A=[$($array:tt)*], K=[$($key:tt)+], R=[: $value:expr]) => {
map!(@ A=[$($array)* ($($key)+, $value),], K=[], R=[])
};
(@ A=[$($array:tt)*], K=[$($key:tt)*], R=[$k:tt $($rest:tt)*]) => {
map!(@ A=[$($array)*], K=[$($key)* $k], R=[$($rest)*])
};
(@ A=[$($array:tt)*], K=[$($key:tt)*], R=[]) => {
compile_error!(concat!("missing value for key ", stringify!($($key)*)))
};
($($x:tt)*) => {
map!(@ A=[], K=[], R=[$($x)*])
};
} |
It was pointed out to me that there's a macro-free solution, now that we directly implement use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;
fn main() {
// Rust 1.53
let s = Vec::from_iter([1, 2, 3]);
println!("{:?}", s);
let s = BTreeSet::from_iter([1, 2, 3]);
println!("{:?}", s);
let s = HashSet::<_>::from_iter([1, 2, 3]);
println!("{:?}", s);
let s = BTreeMap::from_iter([(1, 2), (3, 4)]);
println!("{:?}", s);
let s = HashMap::<_, _>::from_iter([(1, 2), (3, 4)]);
println!("{:?}", s);
} Previous versionHere, I've used a #![feature(array_value_iter, const_generics, const_generic_impls_guard)]
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;
fn main() {
let s = Vec::from_iter(Shim([1, 2, 3]));
println!("{:?}", s);
let s = BTreeSet::from_iter(Shim([1, 2, 3]));
println!("{:?}", s);
let s = HashSet::<_>::from_iter(Shim([1, 2, 3]));
println!("{:?}", s);
let s = BTreeMap::from_iter(Shim([(1, 2), (3, 4)]));
println!("{:?}", s);
let s = HashMap::<_, _>::from_iter(Shim([(1, 2), (3, 4)]));
println!("{:?}", s);
}
struct Shim<T, const N: usize>([T; N]);
impl<T, const N: usize> IntoIterator for Shim<T, { N }>
where
[T; { N }]: std::array::LengthAtMost32,
{
type Item = T;
type IntoIter = std::array::IntoIter<T, { N }>;
fn into_iter(self) -> Self::IntoIter {
std::array::IntoIter::new(self.0)
}
} See also the macro version. |
Some competition on the external crate front: https://docs.rs/velcro It uses
And since owned
oh, and the
|
Any solution involving an intermediate array, like the two that @shepmaster posted, runs the risk of the optimizer not optimizing the array out. And if the array remains, that also leads to value being read and memmoved from the array to its final place. This means wasted space for an array that is immediately consumed, as well as unnecessary byte copying, both of which matter for containers with lots of values or large values. Eg https://rust.godbolt.org/z/9eoWEa - Of course anyone using |
I used to really want this macro, but now that rust-lang/rust#84111 is merged, creating a let map = HashMap::from([
("a", 1),
("b", 2),
("c", 3),
]);
let tree = BTreeMap::from([
("a", 1),
("b", 2),
("c", 3),
]);
let hash_set = HashSet::from([1, 2, 3, 4, 5, 6]);
let tree_set = BTreeSet::from([1, 2, 3, 4, 5, 6]); I don't really see a macro as a huge improvement. Formatting is not as nice when the tuple elements are put on separate lines though: let map = HashMap::from([
(
"a",
some_really_long_method_chain_that_is_actually_very_looooong(),
),
("b", foo()),
("c", bar()),
]);
let map: HashMap<_, _> = collection! {
"a" => some_really_long_method_chain_that_is_actually_very_looooong(),
"b" => foo(),
"c" => bar(),
}; And it doesn't look as nice with nested or complex maps: let popular_tech = HashMap::from([
(
"languages",
HashMap::from([
("rust-lang/rust", 58_300_000),
("golang/go", 89_000_000),
("apple/swift", 57_100_000),
]),
),
(
"web-frameworks",
HashMap::from([
("actix/actix-web", 12_000_000),
("gin-gonic/gin", 51_000_000),
("vapor/vapor", 20_600_000),
]),
),
]);
let popular_tech = hashmap! {
"languages" => hashmap! {
"rust-lang/rust" => 58_300_000,
"golang/go" => 89_000_000,
"apple/swift" => 57_100_000,
},
"web-frameworks" => hashmap! {
"actix/actix-web" => 12_000_000,
"gin-gonic/gin" => 51_000_000,
"vapor/vapor" => 20_600_000,
},
}; But for the simple case, which is probably the most common, let popular_tech = HashMap::from([
("languages", HashMap::from([
("rust-lang/rust", 58_300_000),
("golang/go", 89_000_000),
("apple/swift", 57_100_000),
])),
("web-frameworks", HashMap::from([
("actix/actix-web", 12_000_000),
("gin-gonic/gin", 51_000_000),
("vapor/vapor", 20_600_000),
])),
]); I do really like the visual mapping of key to value that the arrow syntax provides, but the above looks really nice as well. |
note that the stabilized if you have a non- |
Issue by gsingh93
Saturday Jun 07, 2014 at 16:49 GMT
For earlier discussion, see rust-lang/rust#14726
This issue was labelled with: A-syntaxext in the Rust repository
I wanted to create an issue first asking about this before submitting a pull request.
Can I go ahead an implement
hashmap!()
,hashset!()
,treemap!()
, andtreeset!()
macros for constructing those collections with the given arguments? The syntax would be:I already have these macros implemented in my own projects, so I'd just have to add them to macros.rs.
If I can add these, is there a process for testing macros? Or would I just replace all occurrences of hash{map,set} and tree{map,set} creation in the tests by the macros?
The text was updated successfully, but these errors were encountered: