Skip to content

Add hashmap, hashset, treemap, and treeset macros #14726

Description

@gsingh93

I wanted to create an issue first asking about this before submitting a pull request.

Can I go ahead an implement hashmap!(), hashset!(), treemap!(), and treeset!() macros for constructing those collections with the given arguments? The syntax would be:

let h = hashmap!("foo" => "bar");
let s = hashset!("foo");

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?

Activity

  1. TyOverby commented on Jun 7, 2014

    @TyOverby
    Contributor

    One problem that I can see off the top of my head is that macros.rs is in the std crate, while all the collections that you are using are in the collections crate. I'm not sure if you could add macros to the collections crate.

    I am strongly in favor of getting these macros added somewhere simply because of how common the collections are used. We need a shortcut.

  2. schmee commented on Jun 7, 2014

    @schmee
    Contributor

    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)
  3. TyOverby commented on Jun 7, 2014

    @TyOverby
    Contributor

    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.

  4. gsingh93 commented on Jun 7, 2014

    @gsingh93
    ContributorAuthor

    @TyOverby Didn't the collections get moved to std?

    I'm fine with either syntax.

  5. TyOverby commented on Jun 7, 2014

    @TyOverby
    Contributor

    Didn't the collections get moved to std?

    Oh wow, they totally did. I wonder if Vec will get moved there too?

  6. huonw commented on Jun 7, 2014

    @huonw
    Contributor

    Vec has always been accessible via std?

    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.

  7. TyOverby commented on Jun 8, 2014

    @TyOverby
    Contributor

    Vec has always been accessible via std

    I meant in std::collections, but I suppose it's more useful in core.

  8. gsingh93 commented on Jun 8, 2014

    @gsingh93
    ContributorAuthor

    @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).

  9. huonw commented on Jun 8, 2014

    @huonw
    Contributor

    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.)

  10. gsingh93 commented on Jun 8, 2014

    @gsingh93
    ContributorAuthor

    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.

  11. gereeter commented on Jun 8, 2014

    @gereeter
    Contributor

    Isn't seq! already implementable using the FromIterator trait?

  12. huonw commented on Jun 9, 2014

    @huonw
    Contributor

    @gereeter yes, but it would be quite ineffficient.

  13. tomjakubowski commented on Jun 9, 2014

    @tomjakubowski
    Contributor

    @huonw Would that macro work? I tried to write a similar one for the MutableMap trait which wouldn't type check because the type of _thing must be known at the insert:

    #![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);
    }
  14. huonw commented on Jun 9, 2014

    @huonw
    Contributor

    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 to let mut _thing: $ty = ... might work; used like map!(TreeMap<int, int>: 1 => 2).

    (With this, even map!(TreeMap<_, _>: 1 => 2) might work.)

  15. 1 remaining item

  16. treeman commented on Jul 27, 2014

    @treeman
    Contributor

    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.

  17. treeman commented on Jul 27, 2014

    @treeman
    Contributor

    It seems like RFC #143 may allow something similar.

  18. japaric commented on Jul 27, 2014

    @japaric
    Contributor

    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 Copy types, if you want to cover non-copy types then you need to replace the map(|&x| x) with map(|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.

  19. treeman commented on Jul 28, 2014

    @treeman
    Contributor

    @japaric that's nice, thanks!

    For it to be accepted into std we might need an efficient implementation though, and preferably one which works interchangeably with Copy and non-copy types (I guess?).

  20. thestinger commented on Aug 4, 2014

    @thestinger
    Contributor

    It should also make use of with_capacity. I suggest extending the container trait to include with_capacity and a generic put function.

  21. brendanzab commented on Aug 8, 2014

    @brendanzab
    Contributor

    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),+))
    )
  22. japaric commented on Aug 8, 2014

    @japaric
    Contributor

    I've crafted a seq! macro (see example in the playpen!) that meets all the requirements stated in this thread:

    • Uses a Seq trait 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_capacity to 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?

  23. treeman commented on Aug 17, 2014

    @treeman
    Contributor

    Nice @japaric, I like it.

    You could write an RFC for this if you want?

  24. japaric commented on Aug 21, 2014

    @japaric
    Contributor

    @treeman I've submitted an RFC.

  25. japaric commented on Dec 24, 2014

    @japaric
    Contributor

    @nick29581 Could you please move this issue to the RFC repo?

  26. rust-highfive commented on Dec 25, 2014

    @rust-highfive
    Contributor

    This issue has been moved to the RFCs repo: rust-lang/rfcs#542

  27. gsingh93 commented on Mar 12, 2015

    @gsingh93
    ContributorAuthor

    @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.

  28. japaric commented on Mar 12, 2015

    @japaric
    Contributor

    @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.

  29. added a commit that references this issue on Jun 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-syntaxextArea: Syntax extensions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions