Repository: rust-lang/regex
Pull request #728 (2020-12-02 07:04:03)

implement `regex::Replacer` for `String`, `&String`, `Cow<'a, str>`, `&Cow<'a, str>`
It is inconvenient to get `&str` in some cases, for example:
```rust
extern crate regex; // 1.4.2
extern crate chrono; // 0.4.19

fn main() {
    let input = "today is time";
    let today = chrono::Local::now();
    let re = regex::Regex::new(r"time").unwrap();
    let s = re.replace(input, &*format!("{}", today)); // <-- I have to use &*format(...)
    println!("{}", s);
}
```

nooberfsh: @BurntSushi  updated.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..ef6cf89
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: An issue with the current behavior of the regex crate.
+title: ''
+labels: ''
+assignees: ''
+---
+
+
+#### What version of regex are you using?
+
+If it isn't the latest version, then please upgrade and check whether the bug
+is still present.
+
+#### Describe the bug at a high level.
+
+Give a brief description of the actual problem you're observing.
+
+#### What are the steps to reproduce the behavior?
+
+This section should almost always provide a COMPLETE Rust program that others
+can compile and run with Cargo. It should be as small as possible.
+
+If providing a small and simple reproduction is not easy, please explain why
+and the maintainers will help you figure out next steps.
+
+#### What is the actual behavior?
+
+If you provide a Rust program in the previous section, then this should be the
+output of that program.
+
+#### What is the expected behavior?
+
+What do you expect the output to be?
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..99fab81
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,7 @@
+blank_issues_enabled: true
+contact_links:
+  - name: Ask a question
+    about: |
+      You've come to seek help or want to discuss something related to the
+      regex crate.
+    url: https://github.com/rust-lang/regex/discussions/new
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..a308352
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest a new feature
+title: ''
+labels: ''
+assignees: ''
+---
+
+
+#### Describe your feature request
+
+Please describe the behavior you want and the motivation. Please also provide
+examples of how the new feature would be used if your feature request were
+added.
+
+The best kind of feature request is one that proposes a concrete change to the
+API along with a sketch of the changes to the public API documentation.
+
+If you're not sure what to write here, then try imagining what the ideal
+documentation of your new feature would look like. Then try to write it.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9dfe497..cf020d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+1.4.3 (2021-01-08)
+==================
+This is a small patch release that adds some missing standard trait
+implementations for some types in the public API.
+
+Bug fixes:
+
+* [BUG #734](https://github.com/rust-lang/regex/pull/734):
+  Add `FusedIterator` and `ExactSizeIterator` impls to iterator types.
+* [BUG #735](https://github.com/rust-lang/regex/pull/735):
+  Add missing `Debug` impls to public API types.
+
+
 1.4.2 (2020-11-01)
 ==================
 This is a small bug fix release that bans `\P{any}`. We previously banned empty
diff --git a/Cargo.toml b/Cargo.toml
index dac60bb..5e1545f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regex"
-version = "1.4.2"  #:version
+version = "1.4.3"  #:version
 authors = ["The Rust Project Developers"]
 license = "MIT OR Apache-2.0"
 readme = "README.md"
@@ -118,7 +118,7 @@ optional = true
 # For parsing regular expressions.
 [dependencies.regex-syntax]
 path = "regex-syntax"
-version = "0.6.21"
+version = "0.6.22"
 default-features = false
 
 [dev-dependencies]
diff --git a/regex-syntax/Cargo.toml b/regex-syntax/Cargo.toml
index 7b56f32..765eb37 100644
--- a/regex-syntax/Cargo.toml
+++ b/regex-syntax/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "regex-syntax"
-version = "0.6.21"  #:version
+version = "0.6.22"  #:version
 authors = ["The Rust Project Developers"]
 license = "MIT/Apache-2.0"
 repository = "https://github.com/rust-lang/regex"
diff --git a/regex-syntax/src/lib.rs b/regex-syntax/src/lib.rs
index f35c913..edf1b99 100644
--- a/regex-syntax/src/lib.rs
+++ b/regex-syntax/src/lib.rs
@@ -155,6 +155,7 @@ The following features are available:
 */
 
 #![deny(missing_docs)]
+#![warn(missing_debug_implementations)]
 #![forbid(unsafe_code)]
 
 pub use error::{Error, Result};
diff --git a/regex-syntax/src/utf8.rs b/regex-syntax/src/utf8.rs
index 489729e..89862af 100644
--- a/regex-syntax/src/utf8.rs
+++ b/regex-syntax/src/utf8.rs
@@ -84,6 +84,7 @@ which uses it for executing automata on their term index.
 
 use std::char;
 use std::fmt;
+use std::iter::FusedIterator;
 use std::slice;
 
 const MAX_UTF8_BYTES: usize = 4;
@@ -295,6 +296,7 @@ impl fmt::Debug for Utf8Range {
 /// illustrative. In practice, you could just try to decode your byte sequence
 /// and compare it with the scalar value range directly. However, this is not
 /// always possible (for example, in a byte based automaton).
+#[derive(Debug)]
 pub struct Utf8Sequences {
     range_stack: Vec<ScalarRange>,
 }
@@ -388,6 +390,8 @@ impl Iterator for Utf8Sequences {
     }
 }
 
+impl FusedIterator for Utf8Sequences {}
+
 impl ScalarRange {
     /// split splits this range if it overlaps with a surrogate codepoint.
     ///
diff --git a/src/compile.rs b/src/compile.rs
index cdc583c..9ffd347 100644
--- a/src/compile.rs
+++ b/src/compile.rs
@@ -1,4 +1,5 @@
 use std::collections::HashMap;
+use std::fmt;
 use std::iter;
 use std::result;
 use std::sync::Arc;
@@ -25,6 +26,9 @@ struct Patch {
 
 /// A compiler translates a regular expression AST to a sequence of
 /// instructions. The sequence of instructions represents an NFA.
+// `Compiler` is only public via the `internal` module, so avoid deriving
+// `Debug`.
+#[allow(missing_debug_implementations)]
 pub struct Compiler {
     insts: Vec<MaybeInst>,
     compiled: Program,
@@ -1051,6 +1055,7 @@ impl<'a, 'b> CompileClass<'a, 'b> {
 /// This uses similar idea to [`SparseSet`](../sparse/struct.SparseSet.html),
 /// except it uses hashes as original indices and then compares full keys for
 /// validation against `dense` array.
+#[derive(Debug)]
 struct SuffixCache {
     sparse: Box<[usize]>,
     dense: Vec<SuffixCacheEntry>,
@@ -1159,6 +1164,12 @@ impl ByteClassSet {
     }
 }
 
+impl fmt::Debug for ByteClassSet {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_tuple("ByteClassSet").field(&&self.0[..]).finish()
+    }
+}
+
 fn u32_to_usize(n: u32) -> usize {
     // In case usize is less than 32 bits, we need to guard against overflow.
     // On most platforms this compiles to nothing.
diff --git a/src/exec.rs b/src/exec.rs
index acca2dc..e1aae87 100644
--- a/src/exec.rs
+++ b/src/exec.rs
@@ -30,6 +30,7 @@ use utf8::next_utf8;
 /// In particular, this manages the various compiled forms of a single regular
 /// expression and the choice of which matching engine to use to execute a
 /// regular expression.
+#[derive(Debug)]
 pub struct Exec {
     /// All read only state.
     ro: Arc<ExecReadOnly>,
@@ -49,6 +50,7 @@ pub struct ExecNoSync<'c> {
 }
 
 /// `ExecNoSyncStr` is like `ExecNoSync`, but matches on &str instead of &[u8].
+#[derive(Debug)]
 pub struct ExecNoSyncStr<'c>(ExecNoSync<'c>);
 
 /// `ExecReadOnly` comprises all read only state for a regex. Namely, all such
@@ -97,6 +99,9 @@ struct ExecReadOnly {
 /// Facilitates the construction of an executor by exposing various knobs
 /// to control how a regex is executed and what kinds of resources it's
 /// permitted to use.
+// `ExecBuilder` is only public via the `internal` module, so avoid deriving
+// `Debug`.
+#[allow(missing_debug_implementations)]
 pub struct ExecBuilder {
     options: RegexOptions,
     match_type: Option<MatchType>,
diff --git a/src/lib.rs b/src/lib.rs
index bdcebd4..d3dc58d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -616,6 +616,7 @@ another matching engine with fixed memory requirements.
 #![deny(missing_docs)]
 #![cfg_attr(test, deny(warnings))]
 #![cfg_attr(feature = "pattern", feature(pattern))]
+#![warn(missing_debug_implementations)]
 
 #[cfg(not(feature = "std"))]
 compile_error!("`std` feature is currently required to build this crate");
diff --git a/src/literal/imp.rs b/src/literal/imp.rs
index fe07ffc..e4d04ed 100644
--- a/src/literal/imp.rs
+++ b/src/literal/imp.rs
@@ -72,7 +72,7 @@ impl LiteralSearcher {
     /// Returns true if all matches comprise the entire regular expression.
     ///
     /// This does not necessarily mean that a literal match implies a match
-    /// of the regular expression. For example, the regular expresison `^a`
+    /// of the regular expression. For example, the regular expression `^a`
     /// is comprised of a single complete literal `a`, but the regular
     /// expression demands that it only match at the beginning of a string.
     pub fn complete(&self) -> bool {
@@ -232,6 +232,7 @@ impl Matcher {
     }
 }
 
+#[derive(Debug)]
 pub enum LiteralIter<'a> {
     Empty,
     Bytes(&'a [u8]),
diff --git a/src/re_builder.rs b/src/re_builder.rs
index 3fef99d..fc140f8 100644
--- a/src/re_builder.rs
+++ b/src/re_builder.rs
@@ -47,6 +47,7 @@ macro_rules! define_builder {
             /// A builder can be used to configure how the regex is built, for example, by
             /// setting the default flags (which can be overridden in the expression
             /// itself) or setting various limits.
+            #[derive(Debug)]
             pub struct RegexBuilder(RegexOptions);
 
             impl RegexBuilder {
@@ -244,6 +245,7 @@ macro_rules! define_set_builder {
             /// A builder can be used to configure how the regexes are built, for example,
             /// by setting the default flags (which can be overridden in the expression
             /// itself) or setting various limits.
+            #[derive(Debug)]
             pub struct RegexSetBuilder(RegexOptions);
 
             impl RegexSetBuilder {
diff --git a/src/re_bytes.rs b/src/re_bytes.rs
index ca01e0e..204a70a 100644
--- a/src/re_bytes.rs
+++ b/src/re_bytes.rs
@@ -1,6 +1,7 @@
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::fmt;
+use std::iter::FusedIterator;
 use std::ops::{Index, Range};
 use std::str::FromStr;
 use std::sync::Arc;
@@ -690,6 +691,7 @@ impl Regex {
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the matched byte string.
+#[derive(Debug)]
 pub struct Matches<'r, 't>(re_trait::Matches<'t, ExecNoSync<'r>>);
 
 impl<'r, 't> Iterator for Matches<'r, 't> {
@@ -701,6 +703,8 @@ impl<'r, 't> Iterator for Matches<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for Matches<'r, 't> {}
+
 /// An iterator that yields all non-overlapping capture groups matching a
 /// particular regular expression.
 ///
@@ -708,6 +712,7 @@ impl<'r, 't> Iterator for Matches<'r, 't> {
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the matched byte string.
+#[derive(Debug)]
 pub struct CaptureMatches<'r, 't>(
     re_trait::CaptureMatches<'t, ExecNoSync<'r>>,
 );
@@ -724,10 +729,13 @@ impl<'r, 't> Iterator for CaptureMatches<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for CaptureMatches<'r, 't> {}
+
 /// Yields all substrings delimited by a regular expression match.
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the byte string being split.
+#[derive(Debug)]
 pub struct Split<'r, 't> {
     finder: Matches<'r, 't>,
     last: usize,
@@ -757,12 +765,15 @@ impl<'r, 't> Iterator for Split<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for Split<'r, 't> {}
+
 /// Yields at most `N` substrings delimited by a regular expression match.
 ///
 /// The last substring will be whatever remains after splitting.
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the byte string being split.
+#[derive(Debug)]
 pub struct SplitN<'r, 't> {
     splits: Split<'r, 't>,
     n: usize,
@@ -790,14 +801,21 @@ impl<'r, 't> Iterator for SplitN<'r, 't> {
             Some(&text[self.splits.last..])
         }
     }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (0, Some(self.n))
+    }
 }
 
+impl<'r, 't> FusedIterator for SplitN<'r, 't> {}
+
 /// An iterator over the names of all possible captures.
 ///
 /// `None` indicates an unnamed capture; the first element (capture 0, the
 /// whole matched region) is always unnamed.
 ///
 /// `'r` is the lifetime of the compiled regular expression.
+#[derive(Clone, Debug)]
 pub struct CaptureNames<'r>(::std::slice::Iter<'r, Option<String>>);
 
 impl<'r> Iterator for CaptureNames<'r> {
@@ -813,8 +831,16 @@ impl<'r> Iterator for CaptureNames<'r> {
     fn size_hint(&self) -> (usize, Option<usize>) {
         self.0.size_hint()
     }
+
+    fn count(self) -> usize {
+        self.0.count()
+    }
 }
 
+impl<'r> ExactSizeIterator for CaptureNames<'r> {}
+
+impl<'r> FusedIterator for CaptureNames<'r> {}
+
 /// CaptureLocations is a low level representation of the raw offsets of each
 /// submatch.
 ///
@@ -1057,7 +1083,7 @@ impl<'t, 'i> Index<&'i str> for Captures<'t> {
 ///
 /// The lifetime `'c` corresponds to the lifetime of the `Captures` value, and
 /// the lifetime `'t` corresponds to the originally matched text.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct SubCaptureMatches<'c, 't: 'c> {
     caps: &'c Captures<'t>,
     it: SubCapturesPosIter<'c>,
@@ -1073,13 +1099,15 @@ impl<'c, 't> Iterator for SubCaptureMatches<'c, 't> {
     }
 }
 
+impl<'c, 't> FusedIterator for SubCaptureMatches<'c, 't> {}
+
 /// Replacer describes types that can be used to replace matches in a byte
 /// string.
 ///
 /// In general, users of this crate shouldn't need to implement this trait,
-/// since implementations are already provided for `&[u8]` and
-/// `FnMut(&Captures) -> Vec<u8>` (or any `FnMut(&Captures) -> T`
-/// where `T: AsRef<[u8]>`), which covers most use cases.
+/// since implementations are already provided for `&[u8]` along with other
+/// variants of bytes types and `FnMut(&Captures) -> Vec<u8>` (or any
+/// `FnMut(&Captures) -> T` where `T: AsRef<[u8]>`), which covers most use cases.
 pub trait Replacer {
     /// Appends text to `dst` to replace the current match.
     ///
@@ -1148,10 +1176,55 @@ impl<'a> Replacer for &'a [u8] {
     }
 
     fn no_expansion(&mut self) -> Option<Cow<[u8]>> {
-        match find_byte(b'$', *self) {
-            Some(_) => None,
-            None => Some(Cow::Borrowed(*self)),
-        }
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for &'a Vec<u8> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
+        caps.expand(*self, dst);
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<[u8]>> {
+        no_expansion(self)
+    }
+}
+
+impl Replacer for Vec<u8> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
+        caps.expand(self, dst);
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<[u8]>> {
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for Cow<'a, [u8]> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
+        caps.expand(self.as_ref(), dst);
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<[u8]>> {
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for &'a Cow<'a, [u8]> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
+        caps.expand(self.as_ref(), dst);
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<[u8]>> {
+        no_expansion(self)
+    }
+}
+
+fn no_expansion<T: AsRef<[u8]>>(t: &T) -> Option<Cow<[u8]>> {
+    let s = t.as_ref();
+    match find_byte(b'$', s) {
+        Some(_) => None,
+        None => Some(Cow::Borrowed(s)),
     }
 }
 
@@ -1173,6 +1246,7 @@ where
 /// and performant (since capture groups don't need to be found).
 ///
 /// `'t` is the lifetime of the literal text.
+#[derive(Clone, Debug)]
 pub struct NoExpand<'t>(pub &'t [u8]);
 
 impl<'t> Replacer for NoExpand<'t> {
diff --git a/src/re_set.rs b/src/re_set.rs
index b8954be..0a00229 100644
--- a/src/re_set.rs
+++ b/src/re_set.rs
@@ -320,6 +320,7 @@ impl<'a> IntoIterator for &'a SetMatches {
 /// This will always produces matches in ascending order of index, where the
 /// index corresponds to the index of the regex that matched with respect to
 /// its position when initially building the set.
+#[derive(Debug)]
 pub struct SetMatchesIntoIter(iter::Enumerate<vec::IntoIter<bool>>);
 
 impl Iterator for SetMatchesIntoIter {
@@ -352,6 +353,8 @@ impl DoubleEndedIterator for SetMatchesIntoIter {
     }
 }
 
+impl iter::FusedIterator for SetMatchesIntoIter {}
+
 /// A borrowed iterator over the set of matches from a regex set.
 ///
 /// The lifetime `'a` refers to the lifetime of a `SetMatches` value.
@@ -359,7 +362,7 @@ impl DoubleEndedIterator for SetMatchesIntoIter {
 /// This will always produces matches in ascending order of index, where the
 /// index corresponds to the index of the regex that matched with respect to
 /// its position when initially building the set.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct SetMatchesIter<'a>(iter::Enumerate<slice::Iter<'a, bool>>);
 
 impl<'a> Iterator for SetMatchesIter<'a> {
@@ -392,6 +395,8 @@ impl<'a> DoubleEndedIterator for SetMatchesIter<'a> {
     }
 }
 
+impl<'a> iter::FusedIterator for SetMatchesIter<'a> {}
+
 #[doc(hidden)]
 impl From<Exec> for RegexSet {
     fn from(exec: Exec) -> Self {
diff --git a/src/re_trait.rs b/src/re_trait.rs
index d14a9f7..ea6be9c 100644
--- a/src/re_trait.rs
+++ b/src/re_trait.rs
@@ -1,3 +1,6 @@
+use std::fmt;
+use std::iter::FusedIterator;
+
 /// Slot is a single saved capture location. Note that there are two slots for
 /// every capture in a regular expression (one slot each for the start and end
 /// of the capture).
@@ -51,7 +54,7 @@ impl Locations {
 /// Positions are byte indices in terms of the original string matched.
 ///
 /// `'c` is the lifetime of the captures.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct SubCapturesPosIter<'c> {
     idx: usize,
     locs: &'c Locations,
@@ -73,6 +76,8 @@ impl<'c> Iterator for SubCapturesPosIter<'c> {
     }
 }
 
+impl<'c> FusedIterator for SubCapturesPosIter<'c> {}
+
 /// `RegularExpression` describes types that can implement regex searching.
 ///
 /// This trait is my attempt at reducing code duplication and to standardize
@@ -85,9 +90,9 @@ impl<'c> Iterator for SubCapturesPosIter<'c> {
 /// somewhat reasonable. One particular thing this trait would expose would be
 /// the ability to start the search of a regex anywhere in a haystack, which
 /// isn't possible in the current public API.
-pub trait RegularExpression: Sized {
+pub trait RegularExpression: Sized + fmt::Debug {
     /// The type of the haystack.
-    type Text: ?Sized;
+    type Text: ?Sized + fmt::Debug;
 
     /// The number of capture slots in the compiled regular expression. This is
     /// always two times the number of capture groups (two slots per group).
@@ -145,6 +150,7 @@ pub trait RegularExpression: Sized {
 }
 
 /// An iterator over all non-overlapping successive leftmost-first matches.
+#[derive(Debug)]
 pub struct Matches<'t, R>
 where
     R: RegularExpression,
@@ -205,8 +211,16 @@ where
     }
 }
 
+impl<'t, R> FusedIterator for Matches<'t, R>
+where
+    R: RegularExpression,
+    R::Text: 't + AsRef<[u8]>,
+{
+}
+
 /// An iterator over all non-overlapping successive leftmost-first matches with
 /// captures.
+#[derive(Debug)]
 pub struct CaptureMatches<'t, R>(Matches<'t, R>)
 where
     R: RegularExpression,
@@ -260,3 +274,10 @@ where
         Some(locs)
     }
 }
+
+impl<'t, R> FusedIterator for CaptureMatches<'t, R>
+where
+    R: RegularExpression,
+    R::Text: 't + AsRef<[u8]>,
+{
+}
diff --git a/src/re_unicode.rs b/src/re_unicode.rs
index ea95c1b..1b478cd 100644
--- a/src/re_unicode.rs
+++ b/src/re_unicode.rs
@@ -1,6 +1,7 @@
 use std::borrow::Cow;
 use std::collections::HashMap;
 use std::fmt;
+use std::iter::FusedIterator;
 use std::ops::{Index, Range};
 use std::str::FromStr;
 use std::sync::Arc;
@@ -747,6 +748,7 @@ impl Regex {
 /// whole matched region) is always unnamed.
 ///
 /// `'r` is the lifetime of the compiled regular expression.
+#[derive(Clone, Debug)]
 pub struct CaptureNames<'r>(::std::slice::Iter<'r, Option<String>>);
 
 impl<'r> Iterator for CaptureNames<'r> {
@@ -762,12 +764,21 @@ impl<'r> Iterator for CaptureNames<'r> {
     fn size_hint(&self) -> (usize, Option<usize>) {
         self.0.size_hint()
     }
+
+    fn count(self) -> usize {
+        self.0.count()
+    }
 }
 
+impl<'r> ExactSizeIterator for CaptureNames<'r> {}
+
+impl<'r> FusedIterator for CaptureNames<'r> {}
+
 /// Yields all substrings delimited by a regular expression match.
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the string being split.
+#[derive(Debug)]
 pub struct Split<'r, 't> {
     finder: Matches<'r, 't>,
     last: usize,
@@ -797,12 +808,15 @@ impl<'r, 't> Iterator for Split<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for Split<'r, 't> {}
+
 /// Yields at most `N` substrings delimited by a regular expression match.
 ///
 /// The last substring will be whatever remains after splitting.
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the string being split.
+#[derive(Debug)]
 pub struct SplitN<'r, 't> {
     splits: Split<'r, 't>,
     n: usize,
@@ -830,8 +844,14 @@ impl<'r, 't> Iterator for SplitN<'r, 't> {
             Some(&text[self.splits.last..])
         }
     }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (0, Some(self.n))
+    }
 }
 
+impl<'r, 't> FusedIterator for SplitN<'r, 't> {}
+
 /// CaptureLocations is a low level representation of the raw offsets of each
 /// submatch.
 ///
@@ -1059,7 +1079,7 @@ impl<'t, 'i> Index<&'i str> for Captures<'t> {
 ///
 /// The lifetime `'c` corresponds to the lifetime of the `Captures` value, and
 /// the lifetime `'t` corresponds to the originally matched text.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct SubCaptureMatches<'c, 't: 'c> {
     caps: &'c Captures<'t>,
     it: SubCapturesPosIter<'c>,
@@ -1075,6 +1095,8 @@ impl<'c, 't> Iterator for SubCaptureMatches<'c, 't> {
     }
 }
 
+impl<'c, 't> FusedIterator for SubCaptureMatches<'c, 't> {}
+
 /// An iterator that yields all non-overlapping capture groups matching a
 /// particular regular expression.
 ///
@@ -1082,6 +1104,7 @@ impl<'c, 't> Iterator for SubCaptureMatches<'c, 't> {
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the matched string.
+#[derive(Debug)]
 pub struct CaptureMatches<'r, 't>(
     re_trait::CaptureMatches<'t, ExecNoSyncStr<'r>>,
 );
@@ -1098,6 +1121,8 @@ impl<'r, 't> Iterator for CaptureMatches<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for CaptureMatches<'r, 't> {}
+
 /// An iterator over all non-overlapping matches for a particular string.
 ///
 /// The iterator yields a `Match` value. The iterator stops when no more
@@ -1105,6 +1130,7 @@ impl<'r, 't> Iterator for CaptureMatches<'r, 't> {
 ///
 /// `'r` is the lifetime of the compiled regular expression and `'t` is the
 /// lifetime of the matched string.
+#[derive(Debug)]
 pub struct Matches<'r, 't>(re_trait::Matches<'t, ExecNoSyncStr<'r>>);
 
 impl<'r, 't> Iterator for Matches<'r, 't> {
@@ -1116,12 +1142,14 @@ impl<'r, 't> Iterator for Matches<'r, 't> {
     }
 }
 
+impl<'r, 't> FusedIterator for Matches<'r, 't> {}
+
 /// Replacer describes types that can be used to replace matches in a string.
 ///
 /// In general, users of this crate shouldn't need to implement this trait,
-/// since implementations are already provided for `&str` and
-/// `FnMut(&Captures) -> String` (or any `FnMut(&Captures) -> T`
-/// where `T: AsRef<str>`), which covers most use cases.
+/// since implementations are already provided for `&str` along with other
+/// variants of string types and `FnMut(&Captures) -> String` (or any
+/// `FnMut(&Captures) -> T` where `T: AsRef<str>`), which covers most use cases.
 pub trait Replacer {
     /// Appends text to `dst` to replace the current match.
     ///
@@ -1190,10 +1218,55 @@ impl<'a> Replacer for &'a str {
     }
 
     fn no_expansion(&mut self) -> Option<Cow<str>> {
-        match find_byte(b'$', self.as_bytes()) {
-            Some(_) => None,
-            None => Some(Cow::Borrowed(*self)),
-        }
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for &'a String {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
+        self.as_str().replace_append(caps, dst)
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<str>> {
+        no_expansion(self)
+    }
+}
+
+impl Replacer for String {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
+        self.as_str().replace_append(caps, dst)
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<str>> {
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for Cow<'a, str> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
+        self.as_ref().replace_append(caps, dst)
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<str>> {
+        no_expansion(self)
+    }
+}
+
+impl<'a> Replacer for &'a Cow<'a, str> {
+    fn replace_append(&mut self, caps: &Captures, dst: &mut String) {
+        self.as_ref().replace_append(caps, dst)
+    }
+
+    fn no_expansion(&mut self) -> Option<Cow<str>> {
+        no_expansion(self)
+    }
+}
+
+fn no_expansion<T: AsRef<str>>(t: &T) -> Option<Cow<str>> {
+    let s = t.as_ref();
+    match find_byte(b'$', s.as_bytes()) {
+        Some(_) => None,
+        None => Some(Cow::Borrowed(s)),
     }
 }
 
@@ -1215,6 +1288,7 @@ where
 /// and performant (since capture groups don't need to be found).
 ///
 /// `'t` is the lifetime of the literal text.
+#[derive(Clone, Debug)]
 pub struct NoExpand<'t>(pub &'t str);
 
 impl<'t> Replacer for NoExpand<'t> {


Test patch:
diff --git a/tests/macros_bytes.rs b/tests/macros_bytes.rs
index 03c370d..3d6c8c3 100644
--- a/tests/macros_bytes.rs
+++ b/tests/macros_bytes.rs
@@ -4,7 +4,6 @@ macro_rules! t { ($re:expr) => { text!($re) } }
 macro_rules! match_text { ($text:expr) => { $text.as_bytes() } }
 macro_rules! use_ { ($($path: tt)*) => { use regex::bytes::$($path)*; } }
 macro_rules! empty_vec { () => { <Vec<&[u8]>>::new() } }
-
 macro_rules! bytes { ($text:expr) => { $text } }
 
 macro_rules! no_expand {
diff --git a/tests/macros_str.rs b/tests/macros_str.rs
index 9b996b3..7b7eb11 100644
--- a/tests/macros_str.rs
+++ b/tests/macros_str.rs
@@ -4,6 +4,7 @@ macro_rules! t { ($text:expr) => { text!($text) } }
 macro_rules! match_text { ($text:expr) => { $text.as_str() } }
 macro_rules! use_ { ($($path: tt)*) => { use regex::$($path)*; } }
 macro_rules! empty_vec { () => { <Vec<&str>>::new() } }
+macro_rules! bytes { ($text:expr) => { std::str::from_utf8($text.as_ref()).unwrap() } }
 
 macro_rules! no_expand {
     ($text:expr) => {{
diff --git a/tests/replace.rs b/tests/replace.rs
index c156a39..700aff2 100644
--- a/tests/replace.rs
+++ b/tests/replace.rs
@@ -130,3 +130,101 @@ replace!(
     t!("${1}a $1a"),
     "ba "
 );
+
+replace!(
+    impl_string,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!("Z".to_string()),
+    "age: Z6"
+);
+replace!(
+    impl_string_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!(&"Z".to_string()),
+    "age: Z6"
+);
+replace!(
+    impl_cow_str_borrowed,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!(std::borrow::Cow::<'_, str>::Borrowed("Z")),
+    "age: Z6"
+);
+replace!(
+    impl_cow_str_borrowed_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!(&std::borrow::Cow::<'_, str>::Borrowed("Z")),
+    "age: Z6"
+);
+replace!(
+    impl_cow_str_owned,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!(std::borrow::Cow::<'_, str>::Owned("Z".to_string())),
+    "age: Z6"
+);
+replace!(
+    impl_cow_str_owned_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    t!(&std::borrow::Cow::<'_, str>::Owned("Z".to_string())),
+    "age: Z6"
+);
+
+replace!(
+    impl_vec_u8,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(vec![b'Z']),
+    "age: Z6"
+);
+replace!(
+    impl_vec_u8_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(&vec![b'Z']),
+    "age: Z6"
+);
+replace!(
+    impl_cow_slice_borrowed,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(std::borrow::Cow::<'_, [u8]>::Borrowed(&[b'Z'])),
+    "age: Z6"
+);
+replace!(
+    impl_cow_slice_borrowed_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(&std::borrow::Cow::<'_, [u8]>::Borrowed(&[b'Z'])),
+    "age: Z6"
+);
+replace!(
+    impl_cow_slice_owned,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(std::borrow::Cow::<'_, [u8]>::Owned(vec![b'Z'])),
+    "age: Z6"
+);
+replace!(
+    impl_cow_slice_owned_ref,
+    replace,
+    r"[0-9]",
+    "age: 26",
+    bytes!(&std::borrow::Cow::<'_, [u8]>::Owned(vec![b'Z'])),
+    "age: Z6"
+);