diff --git a/src/regex/string.rs b/src/regex/string.rs index 65a76740e..6d5b96ab1 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2371,6 +2371,28 @@ impl<'c, 'h> ExactSizeIterator for SubCaptureMatches<'c, 'h> {} impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} +/// Contains helper trait for blanket implementation for [`Replacer`]. +mod replacer_closure { + use super::*; + /// If a closure implements this for all `&'a Captures<'b>`, then it also + /// implements [`Replacer`]. + pub trait ReplacerClosure + where + Self: FnMut(Arg) -> >::Output, + { + /// Return type of the closure (may depend on lifetime `'a` or `'b`). + type Output: AsRef; + } + impl<'a, 'b, F, O> ReplacerClosure<&'a Captures<'b>> for F + where + F: ?Sized + FnMut(&'a Captures<'b>) -> O, + O: AsRef, + { + type Output = O; + } +} +use replacer_closure::*; + /// A trait for types that can be used to replace matches in a haystack. /// /// In general, users of this crate shouldn't need to implement this trait, @@ -2403,6 +2425,49 @@ impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} /// let result = re.replace("Springsteen, Bruce", NameSwapper); /// assert_eq!(result, "Bruce Springsteen"); /// ``` +/// +/// # Implementation by closures +/// +/// Closures that take an argument of type `&'a Captures<'b>` (for all `'a` +/// and `'b`) and which return a type `T: AsRef` (that may depend on `'a` +/// or `'b`) implement the `Replacer` trait through a [blanket implementation]. +/// +/// [blanket implementation]: Self#impl-Replacer-for-F +/// +/// A simple example looks like this: +/// +/// ``` +/// use regex::{Captures, Regex}; +/// +/// let re = Regex::new(r"[0-9]+").unwrap(); +/// let result = re.replace_all("1234,12345", |caps: &Captures<'_>| { +/// format!("[number with {} digits]", caps[0].len()) +/// }); +/// assert_eq!(result, "[number with 4 digits],[number with 5 digits]"); +/// ``` +/// +/// The return type of the closure may depend on the lifetime of the reference +/// that is passed as an argument to the closure. Using a function, this can be +/// expressed: +/// +/// ``` +/// use regex::{Captures, Regex}; +/// use std::borrow::Cow; +/// +/// let re = Regex::new(r"[0-9]+").unwrap(); +/// fn func<'a, 'b>(caps: &'a Captures<'b>) -> Cow<'a, str> { +/// if caps[0].len() % 2 == 1 { +/// Cow::Owned(format!("0{}", &caps[0])) +/// } else { +/// Cow::Borrowed(&caps[0]) +/// } +/// } +/// let result = re.replace_all("1234,12345", func); +/// assert_eq!(result, "1234,012345"); +/// ``` +/// +/// *Note:* Using a closure instead of a function in the last example can be +/// more tricky and requires a coercing helper function as of yet. pub trait Replacer { /// Appends possibly empty data to `dst` to replace the current match. /// @@ -2501,11 +2566,21 @@ impl<'a> Replacer for &'a Cow<'a, str> { } } -impl Replacer for F -where - F: FnMut(&Captures<'_>) -> T, - T: AsRef, -{ +/// Blanket implementation of `Replacer` for closures. +/// +/// This implementation is basically the following, except that the return type +/// `T` may optionally depend on the lifetimes `'a` and `'b`. +/// +/// ```ignore +/// impl Replacer for F +/// where +/// F: for<'a, 'b> FnMut(&'a Captures<'b>) -> T, +/// T: AsRef, // `T` may depend on `'a` or `'b`, which can't be expressed easily +/// { +/// /* … */ +/// } +/// ``` +impl ReplacerClosure<&'a Captures<'b>>> Replacer for F { fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { dst.push_str((*self)(caps).as_ref()); } diff --git a/tests/misc.rs b/tests/misc.rs index 91e7d2898..9cb7d7746 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -141,3 +141,33 @@ fn dfa_handles_pathological_case() { }; assert!(re.is_match(&text)); } + +// Test if implementation of `Replacer` for closures covers any reasonable +// lifetime combination in regard to the argument and return type. +mod replacer_closure_lifetimes { + use regex::{Captures, Regex}; + use std::borrow::Cow; + #[test] + fn reference_lifetime() { + fn coerce FnMut(&'a Captures<'_>) -> Cow<'a, str>>( + f: F, + ) -> F { + f + } + let s = Regex::new("x") + .unwrap() + .replace_all("x", coerce(|caps| Cow::Borrowed(&caps[0]))); + assert_eq!(s, "x"); + } + #[test] + fn parameter_lifetime() { + fn coerce FnMut(&Captures<'b>) -> Cow<'b, str>>(f: F) -> F { + f + } + let s = Regex::new("x").unwrap().replace_all( + "x", + coerce(|caps| Cow::Borrowed(caps.get(0).unwrap().as_str())), + ); + assert_eq!(s, "x"); + } +}