Skip to content
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 #14726

Closed
gsingh93 opened this issue Jun 7, 2014 · 28 comments
Closed

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

gsingh93 opened this issue Jun 7, 2014 · 28 comments
Labels
A-syntaxext Area: Syntax extensions

Comments

@gsingh93
Copy link
Contributor

gsingh93 commented Jun 7, 2014

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?

@TyOverby
Copy link
Contributor

TyOverby commented Jun 7, 2014

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.

@schmee
Copy link
Contributor

schmee commented Jun 7, 2014

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)

@TyOverby
Copy link
Contributor

TyOverby commented Jun 7, 2014

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.

@gsingh93
Copy link
Contributor Author

gsingh93 commented Jun 7, 2014

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

I'm fine with either syntax.

@TyOverby
Copy link
Contributor

TyOverby commented Jun 7, 2014

Didn't the collections get moved to std?

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

@huonw
Copy link
Member

huonw commented Jun 7, 2014

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.

@TyOverby
Copy link
Contributor

TyOverby commented Jun 8, 2014

Vec has always been accessible via std

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

@gsingh93
Copy link
Contributor Author

gsingh93 commented Jun 8, 2014

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

@huonw
Copy link
Member

huonw commented Jun 8, 2014

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

@gsingh93
Copy link
Contributor Author

gsingh93 commented Jun 8, 2014

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.

@gereeter
Copy link
Contributor

gereeter commented Jun 8, 2014

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

@huonw
Copy link
Member

huonw commented Jun 9, 2014

@gereeter yes, but it would be quite ineffficient.

@tomjakubowski
Copy link
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);
}

@huonw
Copy link
Member

huonw commented Jun 9, 2014

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

@gsingh93
Copy link
Contributor Author

I'd still like to see an RFC for this if anyone has the time.

@treeman
Copy link
Contributor

treeman commented Jul 27, 2014

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.

@treeman
Copy link
Contributor

treeman commented Jul 27, 2014

It seems like RFC #143 may allow something similar.

@japaric
Copy link
Member

japaric commented Jul 27, 2014

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.

@treeman
Copy link
Contributor

treeman commented Jul 28, 2014

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

@thestinger
Copy link
Contributor

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

@brendanzab
Copy link
Member

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),+))
)

@japaric
Copy link
Member

japaric commented Aug 8, 2014

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?

@treeman
Copy link
Contributor

treeman commented Aug 17, 2014

Nice @japaric, I like it.

You could write an RFC for this if you want?

@japaric
Copy link
Member

japaric commented Aug 21, 2014

@treeman I've submitted an RFC.

@japaric
Copy link
Member

japaric commented Dec 24, 2014

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

@rust-highfive
Copy link
Collaborator

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

@gsingh93
Copy link
Contributor Author

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

@japaric
Copy link
Member

japaric commented Mar 12, 2015

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

bors added a commit to rust-lang-ci/rust that referenced this issue Jun 5, 2023
minor: Lock paths-filter action to a specific commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-syntaxext Area: Syntax extensions
Projects
None yet
Development

No branches or pull requests