Loading page…
Rust walkthroughs
Loading page…
{"tool_call":"file.create","args":{"content":"# What are the differences between regex::Regex::captures and regex::Regex::captures_iter for handling multiple matches?
Regex::captures returns a single Captures object representing the first match in the input, providing access to the entire match and all capture groups for that match. Regex::captures_iter returns an iterator that yields a Captures object for each non-overlapping match in the input, allowing you to process all matches sequentially. The key distinction is that captures finds one match and stops, while captures_iter continues finding subsequent matches. Both methods handle capture groups identically—the difference is purely about iteration over multiple match locations. Use captures when you need only the first match or expect exactly one match, and captures_iter when you need to process all matches in the input.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)-(\\w+)\").unwrap();\n let text = \"123-abc 456-def 789-xyz\";\n \n // captures returns Option<Captures>\n if let Some(caps) = re.captures(text) {\n println!(\"Full match: {}\", &caps[0]); // \"123-abc\"\n println!(\"First group: {}\", &caps[1]); // \"123\"\n println!(\"Second group: {}\", &caps[2]); // \"abc\"\n }\n \n // Only the first match is returned\n // \"456-def\" and \"789-xyz\" are not accessible\n}\n\n\ncaptures returns the first match with all its capture groups.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)-(\\w+)\").unwrap();\n let text = \"123-abc 456-def 789-xyz\";\n \n // captures_iter returns an iterator\n for caps in re.captures_iter(text) {\n println!(\"Match: {}, Groups: ({}, {})\", &caps[0], &caps[1], &caps[2]);\n }\n \n // Output:\n // Match: 123-abc, Groups: (123, abc)\n // Match: 456-def, Groups: (456, def)\n // Match: 789-xyz, Groups: (789, xyz)\n}\n\n\ncaptures_iter yields each match with its capture groups.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\w+)@(\\w+)\").unwrap();\n let text = \"a@b c@d e@f\";\n \n // captures: Option<Captures>\n let first: Option<regex::Captures> = re.captures(text);\n \n // captures_iter: CaptureMatches iterator\n let matches: regex::CaptureMatches = re.captures_iter(text);\n \n // CaptureMatches implements Iterator<Item = Captures>\n let all_matches: Vec<regex::Captures> = re.captures_iter(text).collect();\n \n // all_matches.len() == 3\n}\n\n\ncaptures returns Option<Captures>; captures_iter returns an iterator.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)\").unwrap();\n let text = \"Numbers: 10, 20, 30, 40\";\n \n // captures: first match only\n match re.captures(text) {\n Some(caps) => println!(\"First number: {}\", &caps[1]),\n None => println!(\"No numbers found\"),\n }\n // Output: First number: 10\n \n // captures_iter: all matches\n let numbers: Vec<i32> = re.captures_iter(text)\n .map(|caps| caps[1].parse().unwrap())\n .collect();\n \n println!(\"All numbers: {:?}\", numbers);\n // Output: All numbers: [10, 20, 30, 40]\n}\n\n\nUse captures when you need only the first; captures_iter for all matches.
rust\nuse regex::Regex;\n\nfn main() {\n // Both methods find non-overlapping matches\n let re = Regex::new(r\"ana\").unwrap();\n let text = \"banana\";\n \n // captures finds first \"ana\" (at position 1)\n if let Some(caps) = re.captures(text) {\n println!(\"First match: {}\", &caps[0]); // \"ana\" at \"banana[ana]\"\n }\n \n // captures_iter finds all non-overlapping matches\n for caps in re.captures_iter(text) {\n println!(\"Match: {}\", &caps[0]);\n }\n // Only one match found: \"ana\"\n // The \"ana\" at the end overlaps with the first, so it's skipped\n \n // This is the same behavior for both - they find non-overlapping matches\n}\n\n\nBoth methods find non-overlapping matches; captures_iter continues after each match.
rust\nuse regex::Regex;\n\nfn main() {\n // Pattern that can match empty strings\n let re = Regex::new(r\"\").unwrap();\n let text = \"abc\";\n \n // captures: first empty match\n if let Some(caps) = re.captures(text) {\n println!(\"Match at position: {}\", caps.get(0).unwrap().start());\n }\n \n // captures_iter: empty matches at each position\n // But regex crate prevents infinite loops on empty matches\n // It advances at least one character after an empty match\n for (i, caps) in re.captures_iter(text).enumerate() {\n if let Some(m) = caps.get(0) {\n println!(\"Match {}: position {}\", i, m.start());\n }\n }\n // Matches at positions 0, 1, 2, 3 (end of string)\n}\n\n\nBoth handle empty matches; captures_iter advances position between matches.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})\").unwrap();\n let text = \"2023-01-15 and 2024-12-25\";\n \n // Single match: access groups by index or name\n if let Some(caps) = re.captures(text) {\n println!(\"Year: {}\", &caps[1]);\n println!(\"Month: {}\", &caps.name(\"month\").unwrap().as_str());\n println!(\"Day: {}\", &caps[\"day\"]);\n }\n \n // Iterator: same access pattern for each match\n for caps in re.captures_iter(text) {\n println!(\n \"Date: {}-{}-{}\",\n &caps[\"year\"],\n &caps[\"month\"],\n &caps[\"day\"]\n );\n }\n // Output:\n // Date: 2023-01-15\n // Date: 2024-12-25\n}\n\n\nBoth methods provide identical access to capture groups within each match.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\w+)::(\\w+)\").unwrap();\n let text = String::from(\"foo::bar baz::qux\");\n \n // captures borrows the text\n let caps = re.captures(&text).unwrap();\n let match_str: &str = &caps[0]; // borrows from text\n \n // Valid: text is still in scope\n println!(\"Match: {}\", match_str);\n \n // captures_iter borrows text for duration of iteration\n let matches: Vec<&str> = re.captures_iter(&text)\n .map(|caps| caps[0].to_string()) // must own if storing\n .collect();\n \n // Can't return references from iterator that outlive iteration\n}\n\n\nBoth borrow the input text; extracted strings reference the original text.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"\\b\\w+\\b\").unwrap();\n let text = \"hello world rust programming\";\n \n // Using captures: must call repeatedly\n // This doesn't work - captures always starts from beginning\n let first = re.captures(text).map(|c| c[0].to_string());\n let first_again = re.captures(text).map(|c| c[0].to_string());\n // Same result both times!\n \n // Using captures_iter: natural iteration\n let words: Vec<String> = re.captures_iter(text)\n .map(|caps| caps[0].to_string())\n .collect();\n \n println!(\"Words: {:?}\", words);\n // Words: [\"hello\", \"world\", \"rust\", \"programming\"]\n}\n\n\ncaptures_iter is the correct way to get all matches; captures always returns the first.
rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)\").unwrap();\n let text = \"abc 123 def 456 ghi 789\";\n \n // Single match position\n if let Some(caps) = re.captures(text) {\n let m = caps.get(0).unwrap();\n println!(\"First match: '{}' at {}..{}\", m.as_str(), m.start(), m.end());\n // First match: '123' at 4..7\n }\n \n // All match positions\n for caps in re.captures_iter(text) {\n let m = caps.get(0).unwrap();\n let group = caps.get(1).unwrap();\n println!(\"Match '{}' at {}..{}, group at {}..{}\",\n m.as_str(), m.start(), m.end(),\n group.start(), group.end()\n );\n }\n // Match '123' at 4..7, group at 4..7\n // Match '456' at 12..15, group at 12..15\n // Match '789' at 20..23, group at 20..23\n}\n\n\nBoth provide position information via get() method.rust\nuse regex::Regex;\n\nfn main() {\n // Pattern with optional group\n let re = Regex::new(r\"(\\w+)(?:\\s+(\\w+))?\").unwrap();\n let text = \"first second\";\n \n // captures: access optional group\n if let Some(caps) = re.captures(text) {\n println!(\"Required: {}\", &caps[1]);\n if let Some(optional) = caps.get(2) {\n println!(\"Optional: {}\", optional.as_str());\n }\n }\n \n // captures_iter: same pattern per match\n for caps in re.captures_iter(\"one two three four\") {\n println!(\"Required: {}\", &caps[1]);\n match caps.get(2) {\n Some(opt) => println!(\" Optional: {}\", opt.as_str()),\n None => println!(\" No optional\"),\n }\n }\n}\n\n\nBoth handle optional groups identically using get() which returns Option<Match>.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)\").unwrap();\n let text = \"1 2 3 4 5\";\n \n // captures: stops after first match\n // More efficient when you only need first match\n if let Some(caps) = re.captures(text) {\n // Only searches until first match found\n println!(\"First: {}\", &caps[1]);\n }\n \n // captures_iter: finds all matches\n // Only iterate as far as needed\n let first_two: Vec<_> = re.captures_iter(text).take(2).collect();\n // Stops after 2 matches, doesn't find rest\n \n // Using find() for early termination\n let result = re.captures_iter(text)\n .find(|caps| caps[1].len() > 1);\n // Stops at first match with len > 1\n}\n\n\ncaptures may be faster for single match; captures_iter can early-terminate with iterator adapters.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d{4})\").unwrap();\n let text = \"Years: 2020, 2021, 2022, 2023, 2024\";\n \n // Using captures: would need loop\n // captures only returns first match\n \n // Using captures_iter: natural counting\n let count = re.captures_iter(text).count();\n println!(\"Found {} years\", count); // Found 5 years\n \n // With filter\n let recent = re.captures_iter(text)\n .filter(|caps| caps[1].parse::<u32>().unwrap() >= 2022)\n .count();\n println!(\"Recent years: {}\", recent); // Recent years: 3\n}\n\n\ncaptures_iter integrates with iterator methods for counting and filtering.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"\\[(\\w+)\\]\").unwrap();\n let text = \"[foo] [bar] [baz]\";\n \n // Extract all captured group values\n let tags: Vec<String> = re.captures_iter(text)\n .map(|caps| caps[1].to_string())\n .collect();\n \n println!(\"Tags: {:?}\", tags);\n // Tags: [\"foo\", \"bar\", \"baz\"]\n \n // Without captures_iter, this would be awkward\n // captures always returns the first match\n}\n\n\ncaptures_iter is idiomatic for extracting all captured values.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\w+)@(\\w+)\").unwrap();\n let text = \"a@b c@d e@f\";\n \n // find: positions only, no groups\n for m in re.find_iter(text) {\n println!(\"Match: {}\", m.as_str());\n // Cannot access capture groups\n }\n \n // captures: first match with groups\n if let Some(caps) = re.captures(text) {\n println!(\"User: {}, Domain: {}\", &caps[1], &caps[2]);\n }\n \n // captures_iter: all matches with groups\n for caps in re.captures_iter(text) {\n println!(\"User: {}, Domain: {}\", &caps[1], &caps[2]);\n }\n}\n\n\nfind_iter gives positions without groups; captures_iter includes group extraction.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(?P<key>\\w+)=(?P<value>\\w+)\").unwrap();\n let text = \"foo=bar baz=qux num=42\";\n \n // Build a HashMap from captures\n let map: std::collections::HashMap<&str, &str> = re.captures_iter(text)\n .map(|caps| {\n let key = caps.name(\"key\").unwrap().as_str();\n let value = caps.name(\"value\").unwrap().as_str();\n (key, value)\n })\n .collect();\n \n println!(\"Map: {:?}\", map);\n // Map: {\"foo\": \"bar\", \"baz\": \"qux\", \"num\": \"42\"}\n}\n\n\ncaptures_iter enables functional transformation of all matches.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\d+)\").unwrap();\n let text = \"no numbers here\";\n \n // captures: returns None\n match re.captures(text) {\n Some(caps) => println!(\"Found: {}\", &caps[1]),\n None => println!(\"No match found\"),\n }\n \n // captures_iter: empty iterator\n let count = re.captures_iter(text).count();\n println!(\"Match count: {}\", count); // Match count: 0\n \n // Both are empty, but handles differ\n for caps in re.captures_iter(text) {\n // This body never executes\n println!(\"Found: {}\", &caps[1]);\n }\n}\n\n\ncaptures returns None; captures_iter returns an empty iterator.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"((\\w+)@(\\w+\\.\\w+))\").unwrap();\n let text = \"emails: alice@example.com and bob@test.org\";\n \n // Group 0: full match\n // Group 1: outer group (entire email)\n // Group 2: username\n // Group 3: domain\n \n // Single match\n if let Some(caps) = re.captures(text) {\n println!(\"Full: {}\", &caps[0]);\n println!(\"Email: {}\", &caps[1]);\n println!(\"User: {}\", &caps[2]);\n println!(\"Domain: {}\", &caps[3]);\n }\n \n // All matches\n for caps in re.captures_iter(text) {\n println!(\"{} ({}, {})\", &caps[1], &caps[2], &caps[3]);\n }\n}\n\n\nBoth handle nested groups identically; group indices follow pattern order.rust\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r\"(\\w+)\").unwrap();\n let text = \"one two three four five\";\n \n // captures_iter with early termination\n let result = re.captures_iter(text)\n .find(|caps| &caps[1] == \"three\");\n \n if let Some(caps) = result {\n println!(\"Found '{}'!\", &caps[1]);\n }\n \n // Or with try_fold for fallible processing\n let sum: Result<i32, _> = re.captures_iter(text)\n .try_fold(0, |acc, caps| {\n let num: i32 = caps[1].parse()?;\n Ok(acc + num)\n });\n // Fails on \"one\" which isn't a number\n}\n\n\ncaptures_iter supports early termination with iterator combinators.rust\nuse regex::Regex;\n\nfn main() {\n // For multiple patterns, use RegexSet or alternation\n let re = Regex::new(r\"(foo)|(bar)|(baz)\").unwrap();\n let text = \"foo bar baz qux\";\n \n // captures: first match\n if let Some(caps) = re.captures(text) {\n // Which group matched?\n if caps.get(1).is_some() {\n println!(\"Found foo\");\n } else if caps.get(2).is_some() {\n println!(\"Found bar\");\n } else if caps.get(3).is_some() {\n println!(\"Found baz\");\n }\n }\n \n // captures_iter: all matches\n for caps in re.captures_iter(text) {\n let matched = caps.get(1)\n .or_else(|| caps.get(2))\n .or_else(|| caps.get(3))\n .unwrap();\n println!(\"Found: {}\", matched.as_str());\n }\n}\n\n\nBoth work with alternation; determine which group matched with get().captures: Option<Captures<'t>> - first match or None\n- captures_iter: CaptureMatches<'r, 't> - iterator over all matches\n\nWhen to use captures:\n- Need only the first match\n- Expect exactly one match in the input\n- Checking if a pattern matches at all\n- Extracting specific group from first occurrence\n\nWhen to use captures_iter:\n- Processing all matches in input\n- Building collections from captured values\n- Counting or filtering matches\n- Need position information for each match\n\nShared characteristics:\n- Both access capture groups the same way\n- Both borrow input text for returned matches\n- Both find non-overlapping matches\n- Both return Captures objects with same API\n\nKey distinction:\n- captures: finds first, stops searching\n- captures_iter: finds all, yields each\n\nPerformance:\n- captures may be faster for single match (stops early)\n- captures_iter can early-terminate with .take(n) or .find()\n- Both compile the regex once\n\nRelated methods:\n- find / find_iter: positions only, no groups\n- captures / captures_iter: includes capture groups\n\nKey insight: The choice between captures and captures_iter is purely about iteration scope. When you need the first match, captures returns an Option<Captures> that you can immediately pattern-match on. When you need all matches, captures_iter returns an iterator that yields Captures for each non-overlapping match. The Captures object itself is identical in both cases—you access groups via caps[1], caps.name(\"group\"), or caps.get(1) the same way. Use captures when you're looking for "does this pattern match" or "extract the first occurrence"; use captures_iter when you're doing "find all instances" or "extract all matches into a collection". The iterator approach integrates naturally with Rust's iterator ecosystem, allowing you to .map(), .filter(), .take(), and .collect() matches without managing indices or loop state manually.","path":"/articles/263_regex_captures_vs_captures_iter.md"}}