diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 78dd674..b5b5886 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -55,11 +55,14 @@ jobs: - name: Build run: | cargo build --release --target ${{ matrix.target }} + cargo build --release --no-default-features --target ${{ matrix.target }} - name: Clippy run: | cargo clippy --release --target ${{ matrix.target }} + cargo clippy --release --no-default-features --target ${{ matrix.target }} - name: Tests run: | cargo test --release --target ${{ matrix.target }} + cargo test --release --no-default-features --target ${{ matrix.target }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d1436d..d38ffe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.1.1](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.1) - 2022-04-11 + +## Changes + +* Minor performance improvements + +## Fixed + +* Return empty slice when all bytes match trim predicate. +* Markdown (docs) formatting issues. + + + ## [0.1.0](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.0) - 2022-04-11 Initial release! diff --git a/CREDITS.md b/CREDITS.md index e7a5c30..ef608ed 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: trimothy - Version: 0.1.0 - Generated: 2022-04-11 21:07:33 UTC + Version: 0.1.1 + Generated: 2022-04-12 05:23:34 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index 4619024..8bf54a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trimothy" -version = "0.1.0" +version = "0.1.1" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.60" @@ -34,3 +34,8 @@ std = [] name = "fn_trim_slice" harness = false required-features = [ "std" ] + +[[bench]] +name = "fn_trim_mut_vec" +harness = false +required-features = [ "std" ] diff --git a/benches/fn_trim_mut_vec.rs b/benches/fn_trim_mut_vec.rs new file mode 100644 index 0000000..4cd7ac2 --- /dev/null +++ b/benches/fn_trim_mut_vec.rs @@ -0,0 +1,33 @@ +/*! +# Benchmark: Trim Slice +*/ + +use brunch::{ + Bench, + benches, +}; +use trimothy::TrimMut; +use std::time::Duration; + + + +const BYTES: &[u8] = b" \t\nHello World!\n\t "; +const STR: &str = " \t\nHello World!\n\t "; + + + +benches!( + Bench::new("Vec", "trim_mut()") + .timed(Duration::from_secs(1)) + .with_setup(BYTES.to_vec(), |mut v| v.trim_mut()), + + Bench::spacer(), + + Bench::new("String", "trim_mut()") + .timed(Duration::from_secs(1)) + .with_setup(STR.to_owned(), |mut s| s.trim_mut()), + + Bench::new("String.trim()", "to_owned()") + .timed(Duration::from_secs(1)) + .with_setup(STR.to_owned(), |s| s.trim().to_owned()), +); diff --git a/src/lib.rs b/src/lib.rs index ee42978..1dadf9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,3 +108,13 @@ pub use trim_slice::{ TrimSlice, TrimSliceMatches, }; + + + +#[allow(clippy::trivially_copy_pass_by_ref)] // It's the signature iterator wants. +#[inline] +/// # Not Whitespace. +/// +/// This callback is used to find the first or last non-whitespace byte in a +/// slice. It is only split off into its own method to enforce consistency. +pub(crate) const fn not_whitespace(b: &u8) -> bool { ! b.is_ascii_whitespace() } diff --git a/src/trim_mut.rs b/src/trim_mut.rs index 50441ca..a02b99b 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -10,6 +10,7 @@ use alloc::{ }; use crate::{ + not_whitespace, TrimSlice, TrimSliceMatches, }; @@ -28,13 +29,14 @@ use core::intrinsics::copy; /// `Vec`, and `Box<[u8]>`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_mut` | Trim leading and trailing whitespace (mutably). | /// | `trim_start_mut` | Trim leading whitespace (mutably). | /// | `trim_end_mut` | Trim trailing whitespace (mutably). | /// -/// **Note:** these behaviors of these methods are consistent with their immutable +/// **Note:** The behaviors of these methods are consistent with their immutable /// counterparts, meaning that Strings will trim [`char::is_whitespace`], while /// slices will only trim [`u8::is_ascii_whitespace`]. /// @@ -67,15 +69,16 @@ pub trait TrimMut { /// `String`, `Vec`, and `Box<[u8]>`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_matches_mut` | Trim arbitrary leading and trailing bytes via callback (mutably). | /// | `trim_start_matches_mut` | Trim arbitrary leading bytes via callback (mutably). | /// | `trim_end_matches_mut` | Trim arbitrary trailing bytes via callback (mutably). | /// -/// **Note:** The atom being matched varies by implementation to keep behaviors -/// consistent with their immutable counterparts. In other words, `String` uses -/// `char`, while `Vec` and `Box<[u8]>` use `u8`. +/// **Note:** To maintain consistency with their immutable counterparts, the +/// `String` implementation expects callbacks that match a `char`, while the +/// `Vec` and `Box<[u8]>` implementations expect callbacks that match a `u8`. /// /// Refer to the individual implementations for examples. pub trait TrimMatchesMut { @@ -131,18 +134,18 @@ impl TrimMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { let v = self.as_mut_vec(); copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -164,18 +167,18 @@ impl TrimMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { let v = self.as_mut_vec(); copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -223,18 +226,18 @@ impl TrimMatchesMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { let v = self.as_mut_vec(); copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -259,18 +262,18 @@ impl TrimMatchesMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { let v = self.as_mut_vec(); copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -442,22 +445,8 @@ impl TrimMut for Vec { /// assert_eq!(v, b"Hello World!"); /// ``` fn trim_mut(&mut self) { - let trimmed = self.trim(); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. - unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); - self.set_len(trimmed_len); - } - } - } + self.trim_start_mut(); + self.trim_end_mut(); } /// # Trim Start Mut. @@ -474,22 +463,19 @@ impl TrimMut for Vec { /// assert_eq!(v, b"Hello World! "); /// ``` fn trim_start_mut(&mut self) { - let trimmed = self.trim_start(); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { - let trimmed_ptr = trimmed.as_ptr(); + if let Some(start) = self.iter().position(not_whitespace) { + if 0 < start { + let trimmed_len = self.len() - start; - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); + copy(self.as_ptr().add(start), self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); } } } + else { self.truncate(0); } } /// # Trim End Mut. @@ -506,9 +492,10 @@ impl TrimMut for Vec { /// assert_eq!(v, b" Hello World!"); /// ``` fn trim_end_mut(&mut self) { - let trimmed = self.trim_end(); - let trimmed_len = trimmed.len(); - self.truncate(trimmed_len); + if let Some(end) = self.iter().rposition(not_whitespace) { + self.truncate(end + 1); + } + else { self.truncate(0); } } } @@ -536,17 +523,17 @@ impl TrimMatchesMut for Vec { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -567,22 +554,19 @@ impl TrimMatchesMut for Vec { /// ``` fn trim_start_matches_mut(&mut self, cb: F) where F: Fn(Self::MatchUnit) -> bool { - let trimmed = self.trim_start_matches(cb); - let trimmed_len = trimmed.len(); + if let Some(start) = self.iter().position(|b: &u8| ! cb(*b)) { + if 0 < start { + let trimmed_len = self.len() - start; - if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); + copy(self.as_ptr().add(start), self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); } } } + else { self.truncate(0); } } /// # Trim End Matches Mut. @@ -602,8 +586,9 @@ impl TrimMatchesMut for Vec { /// ``` fn trim_end_matches_mut(&mut self, cb: F) where F: Fn(Self::MatchUnit) -> bool { - let trimmed = self.trim_end_matches(cb); - let trimmed_len = trimmed.len(); - self.truncate(trimmed_len); + if let Some(end) = self.iter().rposition(|b: &u8| ! cb(*b)) { + self.truncate(end + 1); + } + else { self.truncate(0); } } } diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 2737331..1add00f 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -8,6 +8,8 @@ use alloc::{ vec::Vec, }; +use crate::not_whitespace; + /// # Trim Slice. @@ -17,13 +19,14 @@ use alloc::{ /// `String`/`&str`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim` | Trim leading and trailing (ASCII) whitespace. | /// | `trim_start` | Trim leading (ASCII) whitespace. | /// | `trim_end` | Trim trailing (ASCII) whitespace. | /// -/// **Note:** because these methods work with individual bytes — rather than chars +/// **Note:** Because these methods work with individual bytes — rather than chars /// — these methods only trim [`u8::is_ascii_whitespace`], not [`char::is_whitespace`]. pub trait TrimSlice { /// # Trim. @@ -78,6 +81,7 @@ pub trait TrimSlice { /// enjoyed by `String`/`&str`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_matches` | Trim arbitrary leading and trailing bytes via callback. | @@ -128,95 +132,20 @@ pub trait TrimSliceMatches { } - -impl TrimSlice for &[u8] { - /// # Trim. - /// - /// Trim leading and trailing (ASCII) whitespace from a slice. - fn trim(&self) -> &[u8] { - let start: usize = self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .unwrap_or(0); - - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or_else(|| &self[start..], |p| &self[start..=p]) - } - - /// # Trim Start. - /// - /// Trim leading (ASCII) whitespace from a slice. - fn trim_start(&self) -> &[u8] { - self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .map_or(self, |p| &self[p..]) - } - - /// # Trim End. - /// - /// Trim trailing (ASCII) whitespace from a slice. - fn trim_end(&self) -> &[u8] { - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or(self, |p| &self[..=p]) - } -} - -impl TrimSliceMatches for &[u8] { - /// # Trim Matches. - /// - /// Trim arbitrary leading and trailing bytes as determined by the provided - /// callback, where a return value of `true` means trim. - fn trim_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - let cb = |b: &u8| ! cb(*b); - - let start: usize = self.iter() - .position(cb) - .unwrap_or(0); - - self.iter() - .rposition(cb) - .map_or_else(|| &self[start..], |p| &self[start..=p]) - } - - /// # Trim Start Matches. - /// - /// Trim arbitrary leading bytes as determined by the provided callback, - /// where a return value of `true` means trim. - fn trim_start_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - self.iter() - .position(|b: &u8| ! cb(*b)) - .map_or(self, |p| &self[p..]) - } - - /// # Trim Start Matches. - /// - /// Trim arbitrary leading bytes as determined by the provided callback, - /// where a return value of `true` means trim. - fn trim_end_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - self.iter() - .rposition(|b: &u8| ! cb(*b)) - .map_or(self, |p| &self[..=p]) - } -} - -macro_rules! trim_slice_alloc { +macro_rules! trim_slice { ($($ty:ty),+ $(,)?) => ($( impl TrimSlice for $ty { /// # Trim. /// /// Trim leading and trailing (ASCII) whitespace from a slice. fn trim(&self) -> &[u8] { - let start: usize = self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .unwrap_or(0); - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + .position(not_whitespace) + .map_or(&[], |start| { + // We know there is an end because there's a beginning. + let end = self.iter().rposition(not_whitespace).unwrap(); + &self[start..=end] + }) } /// # Trim Start. @@ -224,8 +153,8 @@ macro_rules! trim_slice_alloc { /// Trim leading (ASCII) whitespace from a slice. fn trim_start(&self) -> &[u8] { self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .map_or(&self, |p| &self[p..]) + .position(not_whitespace) + .map_or(&[], |p| &self[p..]) } /// # Trim End. @@ -233,8 +162,8 @@ macro_rules! trim_slice_alloc { /// Trim trailing (ASCII) whitespace from a slice. fn trim_end(&self) -> &[u8] { self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or(&self, |p| &self[..=p]) + .rposition(not_whitespace) + .map_or(&[], |p| &self[..=p]) } } @@ -247,13 +176,13 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { let cb = |b: &u8| ! cb(*b); - let start: usize = self.iter() - .position(cb) - .unwrap_or(0); - self.iter() - .rposition(cb) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + .position(cb) + .map_or(&[], |start| { + // We know there is an end because there's a beginning. + let end = self.iter().rposition(cb).unwrap(); + &self[start..=end] + }) } /// # Trim Start Matches. @@ -264,7 +193,7 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { self.iter() .position(|b: &u8| ! cb(*b)) - .map_or(&self, |p| &self[p..]) + .map_or(&[], |p| &self[p..]) } /// # Trim Start Matches. @@ -275,13 +204,13 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { self.iter() .rposition(|b: &u8| ! cb(*b)) - .map_or(&self, |p| &self[..=p]) + .map_or(&[], |p| &self[..=p]) } } )+); } -trim_slice_alloc!(Box<[u8]>, Vec); +trim_slice!(&[u8], Box<[u8]>, Vec); @@ -293,46 +222,74 @@ mod tests { const T_EMPTY: &[u8] = b""; const T_HELLO: &[u8] = b"hello"; const T_HELLO_E: &[u8] = b"hello\t"; - const T_HELLO_S: &[u8] = b"\thello"; - const T_HELLO_SE: &[u8] = b"\n hello \t"; #[test] fn t_trim() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO), - (T_HELLO_E, T_HELLO), - (T_HELLO_SE, T_HELLO), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello"), + ("\thello", "hello"), + ("\n hello world! \t", "hello world!"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim(), expected); - assert_eq!(Box::<[u8]>::from(src).trim(), expected); - assert_eq!(src.to_vec().trim(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim(), b); + + let a = a.to_vec(); + assert_eq!(a.trim(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim(), b); } + assert_eq!(T_EMPTY.trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.trim_matches(|b| b'h' == b), b"ello\t"); - assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_matches(|b| b'h' == b), b"ello\t"); assert_eq!(T_HELLO_E.to_vec().trim_matches(|b| b'h' == b), b"ello\t"); + assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_matches(|b| b'h' == b), b"ello\t"); } #[test] fn t_trim_start() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO), - (T_HELLO_E, T_HELLO_E), - (T_HELLO_SE, b"hello \t"), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello\t"), + ("\thello", "hello"), + ("\n hello world! \t", "hello world! \t"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim_start(), expected); - assert_eq!(Box::<[u8]>::from(src).trim_start(), expected); - assert_eq!(src.to_vec().trim_start(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim_start(), b); + + let a = a.to_vec(); + assert_eq!(a.trim_start(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim_start(), b); } + assert_eq!(T_EMPTY.trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.trim_start_matches(|b| b'h' == b), b"ello\t"); assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_start_matches(|b| b'h' == b), b"ello\t"); assert_eq!(T_HELLO_E.to_vec().trim_start_matches(|b| b'h' == b), b"ello\t"); @@ -340,20 +297,35 @@ mod tests { #[test] fn t_trim_end() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO_S), - (T_HELLO_E, T_HELLO), - (T_HELLO_SE, b"\n hello"), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello"), + ("\thello", "\thello"), + ("\n hello world! \t", "\n hello world!"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim_end(), expected); - assert_eq!(Box::<[u8]>::from(src).trim_end(), expected); - assert_eq!(src.to_vec().trim_end(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim_end(), b); + + let a = a.to_vec(); + assert_eq!(a.trim_end(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim_end(), b); } + assert_eq!(T_EMPTY.trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.trim_matches(|b| b'\t' == b), T_HELLO); assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_matches(|b| b'\t' == b), T_HELLO); assert_eq!(T_HELLO_E.to_vec().trim_matches(|b| b'\t' == b), T_HELLO);