Skip to content

Commit cbbbfde

Browse files
committed
Pre-allocate capacity for maps in json! macro.
Attempt to address issue serde-rs#810. This implementation works by expanding the contents of the map twice, first to increment a capacity variable for each element, then actually inserting them. In debug mode, this implementation does not appear to optimize out the incrementations of the capacity variable, so it is likely not a good solution.
1 parent 829175e commit cbbbfde

File tree

1 file changed

+109
-1
lines changed

1 file changed

+109
-1
lines changed

src/macros.rs

+109-1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,112 @@ macro_rules! json_internal {
234234
json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
235235
};
236236

237+
238+
//////////////////////////////////////////////////////////////////////////
239+
// TT muncher for counting the elements inside of an object {...}.
240+
// Each entry is replaced with `$capacity += 1;`
241+
//
242+
// Must be invoked as: json_internal!(@object_capacity $capacity () ($($tt)*) ($($tt)*))
243+
//
244+
// We require two copies of the input tokens so that we can match on one
245+
// copy and trigger errors on the other copy.
246+
//////////////////////////////////////////////////////////////////////////
247+
248+
// Done.
249+
(@object_capacity $capacity:ident () () ()) => {};
250+
251+
// Current entry followed by trailing comma.
252+
(@object_capacity $capacity:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
253+
$capacity += 1;
254+
json_internal!(@object_capacity $capacity () ($($rest)*) ($($rest)*));
255+
};
256+
257+
// Current entry followed by unexpected token.
258+
(@object_capacity $capacity:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => {
259+
json_unexpected!($unexpected);
260+
};
261+
262+
// Insert the last entry without trailing comma.
263+
(@object_capacity $capacity:ident [$($key:tt)+] ($value:expr)) => {
264+
$capacity += 1;
265+
};
266+
267+
// Next value is `null`.
268+
(@object_capacity $capacity:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
269+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!(null)) $($rest)*)
270+
};
271+
272+
// Next value is `true`.
273+
(@object_capacity $capacity:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
274+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!(true)) $($rest)*)
275+
};
276+
277+
// Next value is `false`.
278+
(@object_capacity $capacity:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
279+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!(false)) $($rest)*)
280+
};
281+
282+
// Next value is an array.
283+
(@object_capacity $capacity:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
284+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!([$($array)*])) $($rest)*)
285+
};
286+
287+
// Next value is a map.
288+
(@object_capacity $capacity:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
289+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!({$($map)*})) $($rest)*)
290+
};
291+
292+
// Next value is an expression followed by comma.
293+
(@object_capacity $capacity:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
294+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!($value)) , $($rest)*)
295+
};
296+
297+
// Last value is an expression with no trailing comma.
298+
(@object_capacity $capacity:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
299+
json_internal!(@object_capacity $capacity [$($key)+] (json_internal!($value)))
300+
};
301+
302+
// Missing value for last entry. Trigger a reasonable error message.
303+
(@object_capacity $capacity:ident ($($key:tt)+) (:) $copy:tt) => {
304+
// "unexpected end of macro invocation"
305+
json_internal!()
306+
};
307+
308+
// Missing colon and value for last entry. Trigger a reasonable error
309+
// message.
310+
(@object_capacity $capacity:ident ($($key:tt)+) () $copy:tt) => {
311+
// "unexpected end of macro invocation"
312+
json_internal!()
313+
};
314+
315+
// Misplaced colon. Trigger a reasonable error message.
316+
(@object_capacity $capacity:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
317+
// Takes no arguments so "no rules expected the token `:`".
318+
json_unexpected!($colon)
319+
};
320+
321+
// Found a comma inside a key. Trigger a reasonable error message.
322+
(@object_capacity $capacity:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
323+
// Takes no arguments so "no rules expected the token `,`".
324+
json_unexpected!($comma)
325+
};
326+
327+
// Key is fully parenthesized. This avoids clippy double_parens false
328+
// positives because the parenthesization may be necessary here.
329+
(@object_capacity $capacity:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
330+
json_internal!(@object_capacity $capacity ($key) (: $($rest)*) (: $($rest)*))
331+
};
332+
333+
// Refuse to absorb colon token into key expression.
334+
(@object_capacity $capacity:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
335+
json_expect_expr_comma!($($unexpected)+)
336+
};
337+
338+
// Munch a token into the current key.
339+
(@object_capacity $capacity:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
340+
json_internal!(@object_capacity $capacity ($($key)* $tt) ($($rest)*) ($($rest)*))
341+
};
342+
237343
//////////////////////////////////////////////////////////////////////////
238344
// The main implementation.
239345
//
@@ -266,7 +372,9 @@ macro_rules! json_internal {
266372

267373
({ $($tt:tt)+ }) => {
268374
$crate::Value::Object({
269-
let mut object = $crate::Map::new();
375+
let mut capacity = 0;
376+
json_internal!(@object_capacity capacity () ($($tt)+) ($($tt)+));
377+
let mut object = $crate::Map::with_capacity(capacity);
270378
json_internal!(@object object () ($($tt)+) ($($tt)+));
271379
object
272380
})

0 commit comments

Comments
 (0)