From e0c8c9519bf12fd797ddee74442bbda85e02696d Mon Sep 17 00:00:00 2001 From: Utkarsh Gupta Date: Wed, 20 May 2026 01:49:39 +0100 Subject: [PATCH] Use slice destructuring --- CHANGELOG.md | 5 +++++ src/flat.rs | 53 ++++++++++++++++++++++++++++---------------------- src/tree.rs | 55 ++++++++++++++++++++++++++++------------------------ 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c9357a..0130c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ All notable changes to this project will be documented in this file. Thanks to [Utkarsh Gupta](https://github.com/utkarshgupta137) for the [PR](https://github.com/danielparks/matchgen/pull/95)! +* Changed generated slice matchers to use slice destructuring bindings instead + of repeated `&slice[N..]` indexing. + + This eliminates extraneous indexing checks. + ## Release 0.4.0 (2025-10-31) * Changed `TreeMatcher` to use a flat slice match, e.g. `match slice { [1, 2, 3, diff --git a/src/flat.rs b/src/flat.rs index e85ba5b..487a3c5 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -237,7 +237,7 @@ impl FlatMatcher { /// r#"fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// #[allow(unreachable_patterns)] /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest @ ..] => (Some(1), rest), /// _ => (None, slice), /// } /// } @@ -272,7 +272,7 @@ impl FlatMatcher { /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// #[allow(unreachable_patterns)] /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest @ ..] => (Some(1), rest), /// _ => (None, slice), /// } /// } @@ -315,7 +315,7 @@ impl FlatMatcher { /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// #[allow(unreachable_patterns)] /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest @ ..] => (Some(1), rest), /// _ => (None, slice), /// } /// } @@ -355,7 +355,7 @@ impl FlatMatcher { /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// #[allow(unreachable_patterns)] /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest @ ..] => (Some(1), rest), /// _ => (None, slice), /// } /// } @@ -493,7 +493,7 @@ impl FlatMatcher { /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// #[allow(unreachable_patterns)] /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest @ ..] => (Some(1), rest), /// _ => (None, slice), /// } /// } @@ -551,24 +551,31 @@ impl FlatMatcher { entries.sort_by_key(|(key, _)| cmp::Reverse(key.len())); for (key, value) in entries { let count = key.len(); - writeln!( - writer, - "{indent} [{prefix}..] => (Some({value}), {remainder}),", - indent = indent, - prefix = if count == 0 { - String::new() - } else { - key.iter() - .map(|&b| crate::fmt_byte(b) + ", ") - .collect::() - }, - value = value, - remainder = if self.return_slice { - format!("&slice[{}..]", count) - } else { - count.to_string() - }, - )?; + let prefix = if count == 0 { + String::new() + } else { + key.iter() + .map(|&b| crate::fmt_byte(b) + ", ") + .collect::() + }; + if self.return_slice { + writeln!( + writer, + "{indent} [{prefix}rest @ ..] => (Some({value}), rest),", + indent = indent, + prefix = prefix, + value = value, + )?; + } else { + writeln!( + writer, + "{indent} [{prefix}..] => (Some({value}), {count}),", + indent = indent, + prefix = prefix, + value = value, + count = count, + )?; + } } write!( diff --git a/src/tree.rs b/src/tree.rs index abfa6c0..a8d3c00 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -152,7 +152,7 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', b'b', b'c', ..] => (Some(1), &slice[3..]), + /// [b'a', b'b', b'c', rest_3 @ ..] => (Some(1), rest_3), /// _ => (None, slice), /// } /// } @@ -181,9 +181,9 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => match &slice[1..] { - /// [b'b', ..] => match &slice[2..] { - /// [b'c', ..] => (Some(1), &slice[3..]), + /// [b'a', rest_1 @ ..] => match rest_1 { + /// [b'b', rest_2 @ ..] => match rest_2 { + /// [b'c', rest_3 @ ..] => (Some(1), rest_3), /// _ => (None, slice), /// } /// _ => (None, slice), @@ -239,7 +239,7 @@ impl TreeMatcher { /// )] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -289,7 +289,7 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -336,7 +336,7 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -380,7 +380,7 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -518,7 +518,7 @@ impl TreeMatcher { /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -656,7 +656,7 @@ where /// #[must_use] /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -1003,7 +1003,7 @@ impl TreeNode { /// "\ /// fn match_bytes(slice: &[u8]) -> (Option, &[u8]) { /// match slice { - /// [b'a', ..] => (Some(1), &slice[1..]), + /// [b'a', rest_1 @ ..] => (Some(1), rest_1), /// _ => (None, slice), /// } /// } @@ -1054,12 +1054,15 @@ impl TreeNode { fallback: Option<(&String, usize)>, collapse_nested_single_arms: bool, ) -> io::Result<()> { - /// Render a subslice operation. + /// Render the binding name for the remainder slice at byte index + /// `i`. Index 0 is the function parameter `slice`; deeper levels + /// are bound by `rest_N @ ..` patterns introduced in this + /// function. #[must_use] #[inline] - fn slice_str(i: usize) -> String { + fn rest_name(i: usize) -> String { if i > 0 { - format!("&slice[{}..]", i) + format!("rest_{}", i) } else { "slice".to_owned() } @@ -1073,27 +1076,27 @@ impl TreeNode { if let Some(leaf) = &node.leaf { writeln!( writer, - "(Some({leaf}), {slice}){comma}", + "(Some({leaf}), {rest}){comma}", leaf = leaf, - slice = slice_str(index), + rest = rest_name(index), comma = comma ) } else if let Some((value, fallback)) = fallback { writeln!( writer, - "(Some({value}), {slice}){comma}", + "(Some({value}), {rest}){comma}", value = value, - slice = slice_str(fallback), + rest = rest_name(fallback), comma = comma ) } else { writeln!(writer, "(None, slice){comma}", comma = comma) } } else { - // `&slice[n..]` returns `[]` when `n == slice.len()`, so as - // long as we return on `[]` in the previous `match`, this will - // never panic. - writeln!(writer, "match {} {{", slice_str(index))?; + // The `rest_N @ ..` binding in the enclosing arm always + // succeeds (it matches any tail, including `[]`), so this + // `match` is safe even when the remainder is empty. + writeln!(writer, "match {} {{", rest_name(index))?; let fallback = node.leaf.as_ref().map(|leaf| (leaf, index)).or(fallback); @@ -1110,19 +1113,21 @@ impl TreeNode { bytes.push(*tuple.0); child = tuple.1; } + let new_index = index.checked_add(bytes.len()).unwrap(); write!( writer, - "{indent} [{bytes}..] => ", + "{indent} [{bytes}{rest} @ ..] => ", indent = indent, bytes = bytes .iter() .map(|&b| crate::fmt_byte(b) + ", ") .collect::(), + rest = rest_name(new_index), )?; render_child( child, writer, - index.checked_add(bytes.len()).unwrap(), + new_index, &indent, fallback, collapse_nested_single_arms, @@ -1130,7 +1135,7 @@ impl TreeNode { } let default = if let Some((value, index)) = fallback { - format!("(Some({}), {})", value, slice_str(index)) + format!("(Some({}), {})", value, rest_name(index)) } else { "(None, slice)".to_owned() };