From f73db821fa8b6e5259e45db219a8e4e4cd5c7ef2 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 10:50:58 +0200 Subject: [PATCH 01/13] More generic impl of Replacer for closures Implement `Replacer` also for closures that return a type which depends on lifetime of the argument. This allows performing a no-op replacement where the closure returns the whole match, for example, without needing to clone the captured string. --- src/regex/string.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 65a76740e..4aa8a75a2 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2371,6 +2371,22 @@ impl<'c, 'h> ExactSizeIterator for SubCaptureMatches<'c, 'h> {} impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} +/// Trait alias for `FnMut` with one argument, which allows adding a bound +/// without specifying the closure's return type. +pub trait GenericFnMut1Arg +where + Self: FnMut(Arg) -> >::Output +{ + /// Return type of the closure. + type Output; +} + +impl GenericFnMut1Arg for T +where T: FnMut(Arg) -> Ret, +{ + type Output = Ret; +} + /// 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, @@ -2501,10 +2517,10 @@ impl<'a> Replacer for &'a Cow<'a, str> { } } -impl Replacer for F +impl Replacer for F where - F: FnMut(&Captures<'_>) -> T, - T: AsRef, + F: for<'a> GenericFnMut1Arg<&'a Captures<'a>>, + for<'a> >>::Output: AsRef, { fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { dst.push_str((*self)(caps).as_ref()); From b6a612d0018a042c9358ef070bfc9c4ad5341d40 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 11:08:14 +0200 Subject: [PATCH 02/13] fixup! More generic impl of Replacer for closures Fixed code formatting using rustfmt. --- src/regex/string.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 4aa8a75a2..0e0b93ed7 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2375,14 +2375,15 @@ impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} /// without specifying the closure's return type. pub trait GenericFnMut1Arg where - Self: FnMut(Arg) -> >::Output + Self: FnMut(Arg) -> >::Output, { /// Return type of the closure. type Output; } impl GenericFnMut1Arg for T -where T: FnMut(Arg) -> Ret, +where + T: FnMut(Arg) -> Ret, { type Output = Ret; } From 092b770af0641f5d61d5c7297d679abeb9a90fb8 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 13:27:32 +0200 Subject: [PATCH 03/13] fixup! More generic impl of Replacer for closures Less generic implementation, which results in a better documented API. --- src/regex/string.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 0e0b93ed7..67f97787c 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2371,21 +2371,22 @@ impl<'c, 'h> ExactSizeIterator for SubCaptureMatches<'c, 'h> {} impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} -/// Trait alias for `FnMut` with one argument, which allows adding a bound -/// without specifying the closure's return type. -pub trait GenericFnMut1Arg +/// If a closure implements this for all `'a`, then it also implements +/// [`Replacer`]. +pub trait ReplacerClosure<'a> where - Self: FnMut(Arg) -> >::Output, + Self: FnMut(&'a Captures<'a>) -> ::Output, { - /// Return type of the closure. - type Output; + /// Return type of the closure (may depend on lifetime `'a`). + type Output: AsRef; } -impl GenericFnMut1Arg for T +impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F where - T: FnMut(Arg) -> Ret, + F: FnMut(&'a Captures<'a>) -> O, + O: AsRef, { - type Output = Ret; + type Output = O; } /// A trait for types that can be used to replace matches in a haystack. @@ -2518,11 +2519,7 @@ impl<'a> Replacer for &'a Cow<'a, str> { } } -impl Replacer for F -where - F: for<'a> GenericFnMut1Arg<&'a Captures<'a>>, - for<'a> >>::Output: AsRef, -{ +impl ReplacerClosure<'a>> Replacer for F { fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { dst.push_str((*self)(caps).as_ref()); } From adf66a8c152c9eb62f14d72ca646ad76c2711c8d Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 14:48:47 +0200 Subject: [PATCH 04/13] fixup! More generic impl of Replacer for closures Do not use the same lifetime for reference and type parameter of `Captures`. --- src/regex/string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 67f97787c..d38290cd3 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2375,7 +2375,7 @@ impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} /// [`Replacer`]. pub trait ReplacerClosure<'a> where - Self: FnMut(&'a Captures<'a>) -> ::Output, + Self: FnMut(&'a Captures<'_>) -> >::Output, { /// Return type of the closure (may depend on lifetime `'a`). type Output: AsRef; @@ -2383,7 +2383,7 @@ where impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F where - F: FnMut(&'a Captures<'a>) -> O, + F: FnMut(&'a Captures<'_>) -> O, O: AsRef, { type Output = O; From a40d8f5dadc91499bb9f525d5fc495fe8759c557 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 20:53:16 +0200 Subject: [PATCH 05/13] fixup! More generic impl of Replacer for closures Hide `ReplacerClosure` trait as an implementation detail and describe for which closures the `Replacer` trait is implemented in the documentation instead. Added documentation tests. --- src/regex/string.rs | 99 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index d38290cd3..8a8fc84a9 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2371,23 +2371,27 @@ impl<'c, 'h> ExactSizeIterator for SubCaptureMatches<'c, 'h> {} impl<'c, 'h> core::iter::FusedIterator for SubCaptureMatches<'c, 'h> {} -/// If a closure implements this for all `'a`, then it also implements -/// [`Replacer`]. -pub trait ReplacerClosure<'a> -where - Self: FnMut(&'a Captures<'_>) -> >::Output, -{ - /// Return type of the closure (may depend on lifetime `'a`). - type Output: AsRef; -} - -impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F -where - F: FnMut(&'a Captures<'_>) -> O, - O: AsRef, -{ - type Output = O; +/// Contains helper trait for blanket implementation for [`Replacer`]. +mod replacer_closure { + use super::*; + /// If a closure implements this for all `'a` and `'b`, then it also + /// implements [`Replacer`]. + pub trait ReplacerClosure<'a> + where + Self: FnMut(&'a Captures<'_>) -> >::Output, + { + /// Return type of the closure (may depend on lifetime `'a`). + type Output: AsRef; + } + impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F + where + F: FnMut(&'a Captures<'_>) -> O, + O: AsRef, + { + type Output = O; + } } +use replacer_closure::*; /// A trait for types that can be used to replace matches in a haystack. /// @@ -2421,6 +2425,69 @@ where /// 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 any `'a` and +/// `'b: 'a` and which return a type `T: AsRef` (that may depend on `'a`) +/// implement the `Replacer` trait through a blanket implementation. +/// +/// 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]"); +/// ``` +/// +/// Note that the return type of the closure may depend on the lifetime of the +/// reference that is passed as an argument to the closure. This requires the +/// closure to be a function, unless [closure lifetime binders] are being used: +/// +/// [closure lifetime binders]: https://rust-lang.github.io/rfcs/3216-closure-lifetime-binder.html +/// [`Cow`]: std::borrow::Cow +/// +/// ``` +/// use regex::{Captures, Regex, Replacer}; +/// use std::borrow::Cow; +/// +/// let re = Regex::new(r"[0-9]+").unwrap(); +/// fn prepend_odd<'a>(caps: &'a Captures<'_>) -> 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", prepend_odd); +/// assert_eq!(result, "1234,012345"); +/// ``` +/// +/// The same example using closure lifetime binders: +/// +/// ``` +/// #![feature(closure_lifetime_binder)] +/// +/// use regex::{Captures, Regex, Replacer}; +/// use std::borrow::Cow; +/// +/// let re = Regex::new(r"[0-9]+").unwrap(); +/// let result = re.replace_all( +/// "1234,12345", +/// for<'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]) +/// } +/// }, +/// ); +/// assert_eq!(result, "1234,012345"); +/// ``` pub trait Replacer { /// Appends possibly empty data to `dst` to replace the current match. /// From de48780b558c75bbeb23f9bf8a5bf868f3183a27 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 21:02:51 +0200 Subject: [PATCH 06/13] fixup! More generic impl of Replacer for closures Fixed error in documentation comment in private module. --- src/regex/string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 8a8fc84a9..e2e77b37c 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2374,8 +2374,8 @@ 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` and `'b`, then it also - /// implements [`Replacer`]. + /// If a closure implements this for all `'a`, then it also implements + /// [`Replacer`]. pub trait ReplacerClosure<'a> where Self: FnMut(&'a Captures<'_>) -> >::Output, From 44515051df7c83742c00119bc32adf85404cb72e Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 21:04:15 +0200 Subject: [PATCH 07/13] fixup! More generic impl of Replacer for closures Ignore documentation test that needs unstable Rust. --- src/regex/string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index e2e77b37c..317d3b20a 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2469,7 +2469,7 @@ use replacer_closure::*; /// /// The same example using closure lifetime binders: /// -/// ``` +/// ```ignore /// #![feature(closure_lifetime_binder)] /// /// use regex::{Captures, Regex, Replacer}; From 43222f2e2e749b5c8c0fa2e5804a13606b8b0f06 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 21:33:46 +0200 Subject: [PATCH 08/13] fixup! More generic impl of Replacer for closures Do not use closure lifetime binder but helper function for coercion in documentation test. --- src/regex/string.rs | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 317d3b20a..ee91a64a8 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2444,47 +2444,31 @@ use replacer_closure::*; /// assert_eq!(result, "[number with 4 digits],[number with 5 digits]"); /// ``` /// -/// Note that the return type of the closure may depend on the lifetime of the -/// reference that is passed as an argument to the closure. This requires the -/// closure to be a function, unless [closure lifetime binders] are being used: +/// The return type of the closure may depend on the lifetime of the reference +/// that is passed as an argument to the closure. Unless [closure lifetime +/// binders] are being used, the correct type of the closure must be known to +/// the compiler, e.g. by coercing it through a helper function: /// /// [closure lifetime binders]: https://rust-lang.github.io/rfcs/3216-closure-lifetime-binder.html -/// [`Cow`]: std::borrow::Cow /// /// ``` /// use regex::{Captures, Regex, Replacer}; /// use std::borrow::Cow; /// -/// let re = Regex::new(r"[0-9]+").unwrap(); -/// fn prepend_odd<'a>(caps: &'a Captures<'_>) -> Cow<'a, str> { -/// if caps[0].len() % 2 == 1 { -/// Cow::Owned(format!("0{}", &caps[0])) -/// } else { -/// Cow::Borrowed(&caps[0]) -/// } +/// fn coerce FnMut(&'a Captures<'_>) -> Cow<'a, str>>(f: F) -> F { +/// f /// } -/// let result = re.replace_all("1234,12345", prepend_odd); -/// assert_eq!(result, "1234,012345"); -/// ``` -/// -/// The same example using closure lifetime binders: -/// -/// ```ignore -/// #![feature(closure_lifetime_binder)] -/// -/// use regex::{Captures, Regex, Replacer}; -/// use std::borrow::Cow; /// /// let re = Regex::new(r"[0-9]+").unwrap(); /// let result = re.replace_all( /// "1234,12345", -/// for<'a, 'b> |caps: &'a Captures<'b>| -> Cow<'a, str> { +/// coerce(|caps| { /// if caps[0].len() % 2 == 1 { /// Cow::Owned(format!("0{}", &caps[0])) /// } else { /// Cow::Borrowed(&caps[0]) /// } -/// }, +/// }), /// ); /// assert_eq!(result, "1234,012345"); /// ``` From 2c8c8ddaa139320e4894ddb094391a13f6123236 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Thu, 20 Jul 2023 22:06:09 +0200 Subject: [PATCH 09/13] fixup! More generic impl of Replacer for closures Added documentation comment on `Replacer` impl for closures. --- src/regex/string.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/regex/string.rs b/src/regex/string.rs index ee91a64a8..08eb705c7 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2570,6 +2570,20 @@ impl<'a> Replacer for &'a Cow<'a, str> { } } +/// Blanket implementation of `Replacer` for closures. +/// +/// This implementation is basically the following, except that the return type +/// `T` may optionally depend on lifetime `'a`. +/// +/// ```ignore +/// impl Replacer for F +/// where +/// F: for<'a> FnMut(&a Captures<'_>) -> T, +/// T: AsRef, // `T` may also depend on `'a`, which cannot be expressed easily +/// { +/// /* … */ +/// } +/// ``` impl ReplacerClosure<'a>> Replacer for F { fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { dst.push_str((*self)(caps).as_ref()); From 657dab858f38894e7f5c76764b69884c5f4e0476 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Fri, 21 Jul 2023 08:15:04 +0200 Subject: [PATCH 10/13] fixup! More generic impl of Replacer for closures Removed unnecessary import of `Replacer` in doc test. --- src/regex/string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index 08eb705c7..a186a1b5e 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2452,7 +2452,7 @@ use replacer_closure::*; /// [closure lifetime binders]: https://rust-lang.github.io/rfcs/3216-closure-lifetime-binder.html /// /// ``` -/// use regex::{Captures, Regex, Replacer}; +/// use regex::{Captures, Regex}; /// use std::borrow::Cow; /// /// fn coerce FnMut(&'a Captures<'_>) -> Cow<'a, str>>(f: F) -> F { From 664a0f2e5a6f843d960b9f7489e225362c34c350 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Fri, 21 Jul 2023 08:41:15 +0200 Subject: [PATCH 11/13] fixup! More generic impl of Replacer for closures Use same lifetime for reference and type parameter of `Captures` (again) because `Captures<'a>` is covariant over `'a`. This covers closures that accept a `&'a Captures<'b>` as argument and have a result type that depends either on `'b` or `'a`. Documentation was updated and corresponding test cases have been added to `tests/misc.rs`. A link to the blanket implementation has been added to the "Implementation by closures" section of the documentation on `Replacer`. --- src/regex/string.rs | 12 +++++++----- tests/misc.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index a186a1b5e..d94f06c54 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2378,14 +2378,14 @@ mod replacer_closure { /// [`Replacer`]. pub trait ReplacerClosure<'a> where - Self: FnMut(&'a Captures<'_>) -> >::Output, + Self: FnMut(&'a Captures<'a>) -> >::Output, { /// Return type of the closure (may depend on lifetime `'a`). type Output: AsRef; } impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F where - F: FnMut(&'a Captures<'_>) -> O, + F: FnMut(&'a Captures<'a>) -> O, O: AsRef, { type Output = O; @@ -2429,8 +2429,10 @@ use replacer_closure::*; /// # Implementation by closures /// /// Closures that take an argument of type `&'a Captures<'b>` for any `'a` and -/// `'b: 'a` and which return a type `T: AsRef` (that may depend on `'a`) -/// implement the `Replacer` trait through a blanket implementation. +/// `'b: 'a` 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: /// @@ -2578,7 +2580,7 @@ impl<'a> Replacer for &'a Cow<'a, str> { /// ```ignore /// impl Replacer for F /// where -/// F: for<'a> FnMut(&a Captures<'_>) -> T, +/// F: for<'a> FnMut(&'a Captures<'a>) -> T, /// T: AsRef, // `T` may also depend on `'a`, which cannot be expressed easily /// { /// /* … */ diff --git a/tests/misc.rs b/tests/misc.rs index 91e7d2898..d4ae31feb 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -141,3 +141,49 @@ 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"); + } + // Additionally demand that its sufficient if the closure accepts a single + // lifetime `'u` which is used both for the reference to and the lifetime + // argument of the `Captures` argument. Note that `Captures<'u>` is + // covariant over `'u`. + #[test] + fn unified_lifetime() { + fn coerce FnMut(&'u Captures<'u>) -> Cow<'u, str>>( + f: F, + ) -> F { + f + } + let s = Regex::new("x") + .unwrap() + .replace_all("x", coerce(|caps| Cow::Borrowed(&caps[0]))); + assert_eq!(s, "x"); + } +} From d89b31a18637e0cecfef1f1d4a73164cb7a9d16d Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Sat, 22 Jul 2023 12:46:55 +0200 Subject: [PATCH 12/13] fixup! More generic impl of Replacer for closures Refactored code to be able to use two different lifetimes `'a` and `'b` for the `&'a Captures<'b>` argument while allowing the return type to depend on either `'a` or `'b`. --- src/regex/string.rs | 26 +++++++++++++------------- tests/misc.rs | 16 ---------------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index d94f06c54..f3829932d 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2374,18 +2374,18 @@ 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`, then it also implements - /// [`Replacer`]. - pub trait ReplacerClosure<'a> + /// If a closure implements this for all `&'a Captures<'b>`, then it also + /// implements [`Replacer`]. + pub trait ReplacerClosure where - Self: FnMut(&'a Captures<'a>) -> >::Output, + Self: FnMut(Arg) -> >::Output, { - /// Return type of the closure (may depend on lifetime `'a`). + /// Return type of the closure (may depend on lifetime `'a` or `'b`). type Output: AsRef; } - impl<'a, F: ?Sized, O> ReplacerClosure<'a> for F + impl<'a, 'b, F, O> ReplacerClosure<&'a Captures<'b>> for F where - F: FnMut(&'a Captures<'a>) -> O, + F: ?Sized + FnMut(&'a Captures<'b>) -> O, O: AsRef, { type Output = O; @@ -2428,8 +2428,8 @@ use replacer_closure::*; /// /// # Implementation by closures /// -/// Closures that take an argument of type `&'a Captures<'b>` for any `'a` and -/// `'b: 'a` and which return a type `T: AsRef` (that may depend on `'a` +/// Closures that take an argument of type `&'a Captures<'b>` (for any `'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 @@ -2575,18 +2575,18 @@ impl<'a> Replacer for &'a Cow<'a, str> { /// Blanket implementation of `Replacer` for closures. /// /// This implementation is basically the following, except that the return type -/// `T` may optionally depend on lifetime `'a`. +/// `T` may optionally depend on the lifetimes `'a` and `'b`. /// /// ```ignore /// impl Replacer for F /// where -/// F: for<'a> FnMut(&'a Captures<'a>) -> T, -/// T: AsRef, // `T` may also depend on `'a`, which cannot be expressed easily +/// 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>> Replacer for F { +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 d4ae31feb..9cb7d7746 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -170,20 +170,4 @@ mod replacer_closure_lifetimes { ); assert_eq!(s, "x"); } - // Additionally demand that its sufficient if the closure accepts a single - // lifetime `'u` which is used both for the reference to and the lifetime - // argument of the `Captures` argument. Note that `Captures<'u>` is - // covariant over `'u`. - #[test] - fn unified_lifetime() { - fn coerce FnMut(&'u Captures<'u>) -> Cow<'u, str>>( - f: F, - ) -> F { - f - } - let s = Regex::new("x") - .unwrap() - .replace_all("x", coerce(|caps| Cow::Borrowed(&caps[0]))); - assert_eq!(s, "x"); - } } From 1aab0b8fdad2ef182c30fb600ff1e19d5c70bc71 Mon Sep 17 00:00:00 2001 From: Jan Behrens Date: Sat, 22 Jul 2023 13:00:02 +0200 Subject: [PATCH 13/13] fixup! More generic impl of Replacer for closures Do not mention unstable features in documentation. Do not include coercing helper function in example code. --- src/regex/string.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/regex/string.rs b/src/regex/string.rs index f3829932d..6d5b96ab1 100644 --- a/src/regex/string.rs +++ b/src/regex/string.rs @@ -2428,7 +2428,7 @@ use replacer_closure::*; /// /// # Implementation by closures /// -/// Closures that take an argument of type `&'a Captures<'b>` (for any `'a` +/// 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]. /// @@ -2447,33 +2447,27 @@ use replacer_closure::*; /// ``` /// /// The return type of the closure may depend on the lifetime of the reference -/// that is passed as an argument to the closure. Unless [closure lifetime -/// binders] are being used, the correct type of the closure must be known to -/// the compiler, e.g. by coercing it through a helper function: -/// -/// [closure lifetime binders]: https://rust-lang.github.io/rfcs/3216-closure-lifetime-binder.html +/// that is passed as an argument to the closure. Using a function, this can be +/// expressed: /// /// ``` /// use regex::{Captures, Regex}; /// use std::borrow::Cow; /// -/// fn coerce FnMut(&'a Captures<'_>) -> Cow<'a, str>>(f: F) -> F { -/// f -/// } -/// /// let re = Regex::new(r"[0-9]+").unwrap(); -/// let result = re.replace_all( -/// "1234,12345", -/// coerce(|caps| { -/// if caps[0].len() % 2 == 1 { -/// Cow::Owned(format!("0{}", &caps[0])) -/// } else { -/// Cow::Borrowed(&caps[0]) -/// } -/// }), -/// ); +/// 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. ///