collect_mac/
lib.rs

1/*!
2This crate provides the `collect!` macro, which can be used to easily construct arbitrary collections, including `Vec`, `String`, and `HashMap`.  It also endeavours to construct the collection with a single allocation, where possible.
3
4## Example
5
6```
7// In the crate root module:
8#[macro_use] extern crate collect_mac;
9
10# use std::collections::{HashMap, HashSet, BTreeMap};
11# fn main() {
12// Initialise an empty collection.
13let a: Vec<i32> = collect![];
14let b: HashMap<String, bool> = collect![];
15
16// Initialise a sequence.
17let c: String = collect!['a', 'b', 'c'];
18
19// Initialise a sequence with a type constraint.
20let d = collect![as HashSet<_>: 0, 1, 2];
21
22// Initialise a map collection.
23let e: BTreeMap<i32, &str> = collect![
24    1 => "one",
25    2 => "two",
26    3 => "many",
27    4 => "lots",
28];
29
30// Initialise a map with a type constraint.
31let f: HashMap<_, u8> = collect![as HashMap<i32, _>: 42 => 0, -11 => 2];
32# }
33```
34
35## Details
36
37The macro supports any collection which implements both the [`Default`][Default] and [`Extend`][Extend] traits.  Specifically, it creates a new, empty collection using `Default`, then calls `Extend` once for each element.
38
39Single-allocation construction is tested and guaranteed for the following standard containers:
40
41* [`HashMap`](http://doc.rust-lang.org/std/collections/struct.HashMap.html)
42* [`HashSet`](http://doc.rust-lang.org/std/collections/struct.HashSet.html)
43* [`String`](http://doc.rust-lang.org/std/string/struct.String.html)
44* [`Vec`](http://doc.rust-lang.org/std/vec/struct.Vec.html)
45* [`VecDeque`](http://doc.rust-lang.org/std/collections/struct.VecDeque.html)
46
47In general, single-allocation construction is done by providing the number of elements through the [`Iterator::size_hint`][Iterator::size_hint] of the *first* call to `Extend`.  The expectation is that the collection will, if possible, pre-allocate enough space for all the elements when it goes to insert the first.
48
49As an example, here is a simplified version of the `Extend` implementation for `Vec`:
50
51```ignore
52impl<T> Extend<T> for Vec<T> {
53    #[inline]
54    fn extend<I: IntoIterator<Item=T>>(&mut self, iterable: I) {
55        let mut iterator = iterable.into_iter();
56        while let Some(element) = iterator.next() {
57            let len = self.len();
58            if len == self.capacity() {
59                let (lower, _) = iterator.size_hint();
60                self.reserve(lower.saturating_add(1));
61            }
62            self.push(element);
63        }
64    }
65}
66```
67
68[Default]: http://doc.rust-lang.org/std/default/trait.Default.html
69[Extend]: http://doc.rust-lang.org/std/iter/trait.Extend.html
70[Iterator::size_hint]: http://doc.rust-lang.org/std/iter/trait.Iterator.html#method.size_hint
71*/
72
73/**
74This macro can be used to easily construct arbitrary collections, including `Vec`, `String`, and `HashMap`.  It also endeavours to construct the collection with a single allocation, where possible.
75
76For more details, see [the crate documentation](./index.html).
77*/
78#[macro_export]
79macro_rules! collect {
80    /*
81    Internal rules.
82    */
83
84    (@count_tts $($tts:tt)*) => {
85        0usize $(+ collect!(@replace_expr $tts 1usize))*
86    };
87
88    (@replace_expr $_tt:tt $sub:expr) => {
89        $sub
90    };
91
92    (@collect
93        ty: $col_ty:ty,
94        es: [$v0:expr, $($vs:expr),* $(,)*],
95        // `cb` is an expression that is inserted after each "step" in constructing the collection.  It largely exists for testing purposes.
96        cb: ($col:ident) $cb:expr,
97    ) => {
98        {
99            const NUM_ELEMS: usize = collect!(@count_tts ($v0) $(($vs))*);
100
101            let mut $col: $col_ty = ::std::default::Default::default();
102
103            $cb;
104
105            let hint = $crate::SizeHintIter {
106                item: Some($v0),
107                count: NUM_ELEMS
108            };
109            ::std::iter::Extend::extend(&mut $col, hint);
110
111            $cb;
112
113            $(
114                ::std::iter::Extend::extend(&mut $col, Some($vs).into_iter());
115                $cb;
116            )*
117
118            $col
119        }
120    };
121
122    /*
123    Public rules.
124    */
125
126    // Short-hands for initialising an empty collection.
127    [] => {
128        collect![as _:]
129    };
130
131    [as $col_ty:ty] => {
132        collect![as $col_ty:]
133    };
134
135    [as $col_ty:ty:] => {
136        {
137            let col: $col_ty = ::std::default::Default::default();
138            col
139        }
140    };
141
142    // Initialise a sequence with a constrained container type.
143    [as $col_ty:ty: $v0:expr] => { collect![as $col_ty: $v0,] };
144
145    [as $col_ty:ty: $v0:expr, $($vs:expr),* $(,)*] => {
146        collect!(
147            @collect
148            ty: $col_ty,
149            es: [$v0, $($vs),*],
150            cb: (col) (),
151        )
152    };
153
154    // Initialise a map with a constrained container type.
155    [as $col_ty:ty: $($ks:expr => $vs:expr),+ $(,)*] => {
156        // Maps implement FromIterator by taking tuples, so we just need to rewrite each `a:b` as `(a,b)`.
157        collect![as $col_ty: $(($ks, $vs)),+]
158    };
159
160    // Initialise a sequence with a fully inferred contained type.
161    [$($vs:expr),+ $(,)*] => {
162        collect![as _: $($vs),+]
163    };
164
165    // Initialise a map with a fully inferred contained type.
166    [$($ks:expr => $vs:expr),+ $(,)*] => {
167        collect![as _: $($ks => $vs),+]
168    };
169}
170
171/**
172This iterator's whole purpose in life is to lie whenever it's asked how many items it has.
173
174This is necessary because of how `Extend` is implemented for `Vec`: specifically, it asks for the first element *before* it checks `size_hint`.  As a result, the old trick (of having an empty iterator that reported a false size hint) doesn't work.
175*/
176#[doc(hidden)]
177pub struct SizeHintIter<T> {
178    pub item: Option<T>,
179    pub count: usize,
180}
181
182impl<T> Iterator for SizeHintIter<T> {
183    type Item = T;
184
185    #[inline]
186    fn next(&mut self) -> Option<T> {
187        match self.item.take() {
188            Some(v) => {
189                self.count -= 1;
190                Some(v)
191            },
192            None => None
193        }
194    }
195
196    #[inline]
197    fn size_hint(&self) -> (usize, Option<usize>) {
198        (self.count, Some(self.count))
199    }
200}