Repository navigation
Add hashmap, hashset, treemap, and treeset macros #14726
Description
Activity
One problem that I can see off the top of my head is thatmacros.rsis in thestdcrate, while all the collections that you are using are in thecollectionscrate. I'm not sure if you could add macros to thecollectionscrate.I am strongly in favor of getting these macros added somewhere simply because of how common the collections are used. We need a shortcut.
If this gets added, I'm strongly in favor of using
:instead of=>in the map macros since it's- shorter
- easier to type
- precedent from Python, Go, Dart, JSON, and even Ruby since 1.9 (and probably others)
If this gets added, I'm strongly in favor of using : instead of =>
I'm not so sure about this. Colons are already used for type annotations and namespaces. I think
->or=>are perfectly acceptable.Reacted by Robin Brown, Krish and Ritwin Narra@TyOverby Didn't the collections get moved to std?
I'm fine with either syntax.
Didn't the collections get moved to std?
Oh wow, they totally did. I wonder if Vec will get moved there too?
Vechas always been accessible viastd?In any case, I think this is a change that requires an RFC. I would prefer having a generic macro (or two of them) that allows constructing any container type, rather than having to manually create one for each type and forcing other data structure implementers to do the same.
Vec has always been accessible via std
I meant in
std::collections, but I suppose it's more useful incore.@huonw How would that generic macro figure out the type of container to create? Would you have to pass in the type?
I can create an RFC if no one else wants to (note: I don't want to, I wouldn't be able to comment on implementation at all).
Using traits, something like
trait Seq<T>: Default { fn add_elem(&mut self, x: T); } macro_rules! seq { ($($e: expr),*) => {{ let mut _thing = Default::default(); $( _thing.add_elem($e); )* _thing }} } let s: HashSet<int> = seq!(1, 2, 3); // HashSet impls Seq<int> let s: HashMap<int, int> = seq!((1, 10), (2, 20), (3, 30); // impls Seq<(int, int)>
(I don't have time to write an RFC atm, maybe in a week and a half.)
Ah, I see. I'll post in this thread next week then to ask about the RFC unless anyone else gets around to writing it.
Isn't
seq!already implementable using theFromIteratortrait?@gereeter yes, but it would be quite ineffficient.
@huonw Would that macro work? I tried to write a similar one for the
MutableMaptrait which wouldn't type check because the type of_thingmust be known at theinsert:#![feature(macro_rules)] extern crate collections; macro_rules! map { ($($k: expr => $v: expr),*) => {{ use std::default::Default; let mut _thing = Default::default(); $( _thing.insert($k, $v); )* //~ ERROR the type of this value must be known in this context _thing }} } pub fn main() { use collections::treemap::TreeMap; let x: TreeMap<int, int> = map!(1 => 2); }
Ah, looks like Rust's current type inference might not be powerful enough (I have no idea if we extend it to make it work).
Something like
($ty: type: $($k: expr ... ))expanding tolet mut _thing: $ty = ...might work; used likemap!(TreeMap<int, int>: 1 => 2).(With this, even
map!(TreeMap<_, _>: 1 => 2)might work.)1 remaining item
I was thinking about something like this. I agree that something generic would be preferred.
We would need to figure out which syntax we would like.
This is a working implementation @huonw mentioned: playpen
Which would give a syntax like:
let map = map!(TreeMap<&str, int>: "a" => 1i, "b" => 2i); let set = set!(TreeSet<int>: 1, 2, 3, 4);Another alternative would be to have a syntax like:
map!( x: TreeMap<&str, int> { "a" => 1i, "b" => 2i } ) set!(x: TreeSet<int> { 1, 2, 3, 4 })However I could not get the macro to recognize
mut. This is what I have currently:macro_rules! map { ($id:ident: $T:ty { $($k: expr => $v: expr),* }) => { // Need a way to specify mutability here! // Not sure how let mut $id = { use std::default::Default; let mut _thing: $T = Default::default(); $( _thing.insert($k, $v); )* _thing } } }Overall I think it would be nicer to have the original syntax idea:
let s: HashSet<int> = seq!(1, 2, 3); // HashSet impls Seq<int> let s: HashMap<int, int> = seq!((1, 10), (2, 20), (3, 30); // impls Seq<(int, int)>But I have no clue what we would have to introduce to make that work.
It seems like RFC #143 may allow something similar.
Overall I think it would be nicer to have the original syntax idea:
let s: HashSet = seq!(1, 2, 3); // HashSet impls Seq
let s: HashMap<int, int> = seq!((1, 10), (2, 20), (3, 30); // impls Seq<(int, int)>
But I have no clue what we would have to introduce to make that work.That macro can be implemented using what we have today without using any new trait:
#![feature(macro_rules)] use std::collections::{HashMap,HashSet}; macro_rules! seq { ($($x:expr),+) => { [$($x,)+].iter().map(|&x| x).collect() } } fn main() { let set: HashSet<int> = seq!(1, 2, 3); let map: HashMap<int, int> = seq!((1, 10), (2, 20), (3, 30)); println!("Set: {}", set); println!("Map: {}", map); }
That macro only works for
Copytypes, if you want to cover non-copy types then you need to replace themap(|&x| x)withmap(|x| x.clone()), but then (for some reason) you need to type annotate the integers with a suffix (at least in the example above).I don't claim that's the most efficient implementation, just want to show that it can be readily implemented today.
@japaric that's nice, thanks!
For it to be accepted into
stdwe might need an efficient implementation though, and preferably one which works interchangeably withCopyand non-copy types (I guess?).It should also make use of
with_capacity. I suggest extending the container trait to includewith_capacityand a genericputfunction.Be sure to implement a rules which allow for trailing commas. For example:
macro_rules! vec( ($($e:expr),*) => ({ // leading _ to allow empty construction without a warning. let mut _temp = ::std::vec::Vec::new(); $(_temp.push($e);)* _temp }); ($($e:expr),+,) => (vec!($($e),+)) )
I've crafted a
seq!macro (see example in the playpen!) that meets all the requirements stated in this thread:- Uses a
Seqtrait and works with the inference engine (as proposed by @huonw) - Provides both a list
[1, 2, 3]and a map{1 => 2, 2 => 3}style (as suggested by @treeman) - Uses
with_capacityto reduce reallocations (as suggested by @thestinger) - Allows trailing commas (as requested by @bjz)
I'm leaving a (minimal) copy of the code here for posteriority:
#![feature(macro_rules)] trait Seq<T> { fn with_capacity(capacity: uint) -> Self; // HACK: A method doesn't play well with the inference engine, use a static function instead fn add_elem(seq: &mut Self, elem: T); } // FIXME (rust-lang/rfcs#88) Use the `$#($arg)` syntax instead of the `count_args!` macro macro_rules! count_args { () => { 0 }; ($x:expr) => { 1 }; ($head:expr, $($tail:expr),+) => { 1 + count_args!($($tail),+) }; } macro_rules! seq { // List style: Vec, {Hash,Tree}Set, etc ($($x:expr),*) => ({ let mut _temp = Seq::with_capacity(count_args!($($x),*)); $(Seq::add_elem(&mut _temp, $x);)* _temp }); // Map style: {Hash,Tree}Map, etc ($($k:expr => $v:expr),*) => ({ let mut _temp = Seq::with_capacity(count_args!($(($k, $v)),*)); $(Seq::add_elem(&mut _temp, ($k, $v));)* _temp }); // Trailing comma <3 ($($x:expr),+,) => { seq!($($x),+) }; ($($k:expr => $v:expr),+,) => { seq!($($k => $v),+) }; }
What do you guys think of this implementation?
- Uses a
Nice @japaric, I like it.
You could write an RFC for this if you want?
@nick29581 Could you please move this issue to the RFC repo?
This issue has been moved to the RFCs repo: rust-lang/rfcs#542
@japaric It'd be nice if you could put the seq implementation in a crate so I could use it throughout my projects without copying and pasting.
@gsingh93 There's a link to my out of tree implementation (i.e. a cargo crate) in the top comment of the RFC. However, I'm not maintaining the crate anymore - I've listed some alternatives in the README though. You can either use one of the listed alternatives or maintain a fork of my original crate.
- added a commit that references this issue
on Jun 5, 2023
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?