From a16a853263789d94877f72f4630e3c30c4ae4d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 06:52:16 +0900 Subject: [PATCH 01/13] init crate --- crates/cow-replace/Cargo.toml | 13 +++++++++++++ crates/cow-replace/src/lib.rs | 1 + 2 files changed, 14 insertions(+) create mode 100644 crates/cow-replace/Cargo.toml create mode 100644 crates/cow-replace/src/lib.rs diff --git a/crates/cow-replace/Cargo.toml b/crates/cow-replace/Cargo.toml new file mode 100644 index 0000000..2e8eeec --- /dev/null +++ b/crates/cow-replace/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = { workspace = true } +description = "String replace with Cow" +edition = { workspace = true } +include = ["Cargo.toml", "src/**/*.rs"] +license = { workspace = true } +name = "cow-replace" +repository = { workspace = true } +version = "0.1.0" + +[features] + +[dependencies] diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/cow-replace/src/lib.rs @@ -0,0 +1 @@ + From f096835e08a67f09df854190079d90644530536a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 06:58:16 +0900 Subject: [PATCH 02/13] autoinherit --- crates/bytes-str/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bytes-str/Cargo.toml b/crates/bytes-str/Cargo.toml index 90e59f7..645a5ca 100644 --- a/crates/bytes-str/Cargo.toml +++ b/crates/bytes-str/Cargo.toml @@ -14,5 +14,5 @@ serde = ["dep:serde"] [dependencies] bytes = { workspace = true } -rkyv = { version = "0.8", optional = true } -serde = { version = "1", optional = true } +rkyv = { workspace = true, optional = true } +serde = { workspace = true, optional = true } From 4549385c9fdb8adee84919f19efb50c19edcd562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 06:58:23 +0900 Subject: [PATCH 03/13] lockfile --- Cargo.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c28691b..4566c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,6 +214,10 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "cow-replace" +version = "0.1.0" + [[package]] name = "crossbeam-deque" version = "0.8.6" From c8c371043642542dbcc52728361d1a31dde4d524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:02:39 +0900 Subject: [PATCH 04/13] Dep --- Cargo.toml | 1 + crates/cow-replace/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index eef3aa4..7a59431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,3 +61,4 @@ tracing-subscriber = "0.3.19" triomphe = "0.1.14" url = "2.5.4" bytes = "1.10.1" +ascii = "1.1.0" diff --git a/crates/cow-replace/Cargo.toml b/crates/cow-replace/Cargo.toml index 2e8eeec..2f29f3d 100644 --- a/crates/cow-replace/Cargo.toml +++ b/crates/cow-replace/Cargo.toml @@ -11,3 +11,4 @@ version = "0.1.0" [features] [dependencies] +ascii = { workspace = true } From 3fedf11195ba58866356c8370a1aa744529f1ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:03:25 +0900 Subject: [PATCH 05/13] lockfile --- Cargo.lock | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4566c50..c7a0c3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "bumpalo" version = "3.18.1" @@ -217,6 +223,9 @@ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "cow-replace" version = "0.1.0" +dependencies = [ + "ascii", +] [[package]] name = "crossbeam-deque" From bbdffa6baae059674dacf6fd79777efa69a02237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:03:28 +0900 Subject: [PATCH 06/13] trait defs --- crates/cow-replace/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index 8b13789..cacdb67 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -1 +1,17 @@ +use std::{borrow::Cow, mem::take}; +use ascii::AsciiChar; + +pub trait ReplaceString { + fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str>; + + fn remove_all_ascii_in_place(&mut self, ch: AsciiChar); + + fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar); + + fn replace_all_str(&mut self, from: &str, to: &str) -> Cow<'_, str>; +} + +impl ReplaceString for String {} + +impl ReplaceString for Cow<'_, str> {} From 5ce39d721d0aecfe5536db970d5b5a04496f81eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:19:49 +0900 Subject: [PATCH 07/13] Impl --- crates/cow-replace/Cargo.toml | 2 +- crates/cow-replace/src/lib.rs | 250 +++++++++++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 5 deletions(-) diff --git a/crates/cow-replace/Cargo.toml b/crates/cow-replace/Cargo.toml index 2f29f3d..a0ceb69 100644 --- a/crates/cow-replace/Cargo.toml +++ b/crates/cow-replace/Cargo.toml @@ -6,7 +6,7 @@ include = ["Cargo.toml", "src/**/*.rs"] license = { workspace = true } name = "cow-replace" repository = { workspace = true } -version = "0.1.0" +version = "0.1.1" [features] diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index cacdb67..92faa96 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, mem::take}; +use std::borrow::Cow; use ascii::AsciiChar; @@ -9,9 +9,251 @@ pub trait ReplaceString { fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar); - fn replace_all_str(&mut self, from: &str, to: &str) -> Cow<'_, str>; + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str>; } -impl ReplaceString for String {} +impl ReplaceString for String { + fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { + let target_byte = ch.as_byte(); + let bytes = self.as_bytes(); -impl ReplaceString for Cow<'_, str> {} + // Check if the character exists first + if !bytes.contains(&target_byte) { + return Cow::Borrowed(self); + } + + // Create new string without the target character + let mut result = String::with_capacity(self.len()); + for &byte in bytes { + if byte != target_byte { + result.push(byte as char); + } + } + + Cow::Owned(result) + } + + fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { + let target_byte = ch.as_byte(); + let bytes = unsafe { self.as_bytes_mut() }; + + let mut write_pos = 0; + let mut read_pos = 0; + + while read_pos < bytes.len() { + if bytes[read_pos] != target_byte { + bytes[write_pos] = bytes[read_pos]; + write_pos += 1; + } + read_pos += 1; + } + + // Truncate to the new length + self.truncate(write_pos); + } + + fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar) { + let from_byte = from.as_byte(); + let to_byte = to.as_byte(); + let bytes = unsafe { self.as_bytes_mut() }; + + for byte in bytes { + if *byte == from_byte { + *byte = to_byte; + } + } + } + + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { + if from.is_empty() || !self.contains(from) { + return Cow::Borrowed(self); + } + + Cow::Owned(self.replace(from, to)) + } +} + +impl ReplaceString for Cow<'_, str> { + fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { + let target_byte = ch.as_byte(); + let bytes = self.as_bytes(); + + // Check if the character exists first + if !bytes.contains(&target_byte) { + return Cow::Borrowed(self); + } + + // Create new string without the target character + let mut result = String::with_capacity(self.len()); + for &byte in bytes { + if byte != target_byte { + result.push(byte as char); + } + } + + Cow::Owned(result) + } + + fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { + match self { + Cow::Borrowed(s) => { + let target_byte = ch.as_byte(); + let bytes = s.as_bytes(); + + if !bytes.contains(&target_byte) { + return; // No changes needed + } + + // Convert to owned and remove + let mut owned = s.to_string(); + owned.remove_all_ascii_in_place(ch); + *self = Cow::Owned(owned); + } + Cow::Owned(s) => { + s.remove_all_ascii_in_place(ch); + } + } + } + + fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar) { + match self { + Cow::Borrowed(s) => { + let from_byte = from.as_byte(); + let bytes = s.as_bytes(); + + if !bytes.contains(&from_byte) { + return; // No changes needed + } + + // Convert to owned and replace + let mut owned = s.to_string(); + owned.replace_all_ascii_in_place(from, to); + *self = Cow::Owned(owned); + } + Cow::Owned(s) => { + s.replace_all_ascii_in_place(from, to); + } + } + } + + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { + if from.is_empty() || !self.contains(from) { + return Cow::Borrowed(self); + } + + Cow::Owned(self.replace(from, to)) + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + + use super::*; + + #[test] + fn test_string_remove_all_ascii() { + let s = "hello world".to_string(); + let result = s.remove_all_ascii(AsciiChar::l); + assert_eq!(result, "heo word"); + + // Test with no occurrences + let s = "hello world".to_string(); + let result = s.remove_all_ascii(AsciiChar::z); + assert_eq!(result, "hello world"); + match result { + Cow::Borrowed(_) => {} + Cow::Owned(_) => panic!("Should return borrowed when no changes"), + } + } + + #[test] + fn test_string_remove_all_ascii_in_place() { + let mut s = "hello world".to_string(); + s.remove_all_ascii_in_place(AsciiChar::l); + assert_eq!(s, "heo word"); + + let mut s = "aaaaaa".to_string(); + s.remove_all_ascii_in_place(AsciiChar::a); + assert_eq!(s, ""); + } + + #[test] + fn test_string_replace_all_ascii_in_place() { + let mut s = "hello world".to_string(); + s.replace_all_ascii_in_place(AsciiChar::l, AsciiChar::x); + assert_eq!(s, "hexxo worxd"); + + let mut s = "hello world".to_string(); + s.replace_all_ascii_in_place(AsciiChar::z, AsciiChar::x); + assert_eq!(s, "hello world"); + } + + #[test] + fn test_string_replace_all_str() { + let s = "hello world hello".to_string(); + let _result = s.replace_all_str("hello", "hi"); + assert_eq!(s, "hi world hi"); + + // Test with no occurrences + let s = "hello world".to_string(); + let result = s.replace_all_str("xyz", "abc"); + match result { + Cow::Borrowed(_) => {} + Cow::Owned(_) => panic!("Should return borrowed when no changes"), + } + assert_eq!(s, "hello world"); + } + + #[test] + fn test_cow_remove_all_ascii() { + let s: Cow<'_, str> = Cow::Borrowed("hello world"); + let result = s.remove_all_ascii(AsciiChar::l); + assert_eq!(result, "heo word"); + + let s: Cow<'_, str> = Cow::Owned("hello world".to_string()); + let result = s.remove_all_ascii(AsciiChar::l); + assert_eq!(result, "heo word"); + } + + #[test] + fn test_cow_remove_all_ascii_in_place() { + let mut s: Cow<'_, str> = Cow::Borrowed("hello world"); + s.remove_all_ascii_in_place(AsciiChar::l); + assert_eq!(s, "heo word"); + match s { + Cow::Owned(_) => {} + Cow::Borrowed(_) => panic!("Should be owned after modification"), + } + + let mut s: Cow<'_, str> = Cow::Owned("hello world".to_string()); + s.remove_all_ascii_in_place(AsciiChar::l); + assert_eq!(s, "heo word"); + } + + #[test] + fn test_cow_replace_all_ascii_in_place() { + let mut s: Cow<'_, str> = Cow::Borrowed("hello world"); + s.replace_all_ascii_in_place(AsciiChar::l, AsciiChar::x); + assert_eq!(s, "hexxo worxd"); + match s { + Cow::Owned(_) => {} + Cow::Borrowed(_) => panic!("Should be owned after modification"), + } + } + + #[test] + fn test_cow_replace_all_str() { + let s: Cow<'_, str> = Cow::Borrowed("hello world hello"); + let _result = s.replace_all_str("hello", "hi"); + assert_eq!(s, "hi world hi"); + + let s: Cow<'_, str> = Cow::Borrowed("hello world"); + let result = s.replace_all_str("xyz", "abc"); + match result { + Cow::Borrowed(_) => {} + Cow::Owned(_) => panic!("Should return borrowed when no changes"), + } + assert_eq!(s, "hello world"); + } +} From b5f25aa1e25205fbe8cbdccb6a474194f120644e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:19:52 +0900 Subject: [PATCH 08/13] lockfile --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c7a0c3b..1bbb311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,7 +222,7 @@ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "cow-replace" -version = "0.1.0" +version = "0.1.1" dependencies = [ "ascii", ] From d2e961d875c648d3f12b93734a82cb8da48d4014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:25:30 +0900 Subject: [PATCH 09/13] more --- crates/cow-replace/src/lib.rs | 105 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index 92faa96..b916fb5 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -2,6 +2,35 @@ use std::borrow::Cow; use ascii::AsciiChar; +// Helper functions for common operations +fn remove_ascii_from_str(s: &str, ch: AsciiChar) -> Option { + let target_byte = ch.as_byte(); + let bytes = s.as_bytes(); + + // Check if the character exists first + if !bytes.contains(&target_byte) { + return None; + } + + // Create new string without the target character + let mut result = String::with_capacity(s.len()); + for &byte in bytes { + if byte != target_byte { + result.push(byte as char); + } + } + + Some(result) +} + +fn replace_str_if_contains(s: &str, from: &str, to: &str) -> Option { + if from.is_empty() || !s.contains(from) { + return None; + } + + Some(s.replace(from, to)) +} + pub trait ReplaceString { fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str>; @@ -14,23 +43,10 @@ pub trait ReplaceString { impl ReplaceString for String { fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - let target_byte = ch.as_byte(); - let bytes = self.as_bytes(); - - // Check if the character exists first - if !bytes.contains(&target_byte) { - return Cow::Borrowed(self); + match remove_ascii_from_str(self, ch) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), } - - // Create new string without the target character - let mut result = String::with_capacity(self.len()); - for &byte in bytes { - if byte != target_byte { - result.push(byte as char); - } - } - - Cow::Owned(result) } fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { @@ -65,49 +81,27 @@ impl ReplaceString for String { } fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - if from.is_empty() || !self.contains(from) { - return Cow::Borrowed(self); + match replace_str_if_contains(self, from, to) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), } - - Cow::Owned(self.replace(from, to)) } } impl ReplaceString for Cow<'_, str> { fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - let target_byte = ch.as_byte(); - let bytes = self.as_bytes(); - - // Check if the character exists first - if !bytes.contains(&target_byte) { - return Cow::Borrowed(self); - } - - // Create new string without the target character - let mut result = String::with_capacity(self.len()); - for &byte in bytes { - if byte != target_byte { - result.push(byte as char); - } + match remove_ascii_from_str(self, ch) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), } - - Cow::Owned(result) } fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { match self { Cow::Borrowed(s) => { - let target_byte = ch.as_byte(); - let bytes = s.as_bytes(); - - if !bytes.contains(&target_byte) { - return; // No changes needed + if let Some(result) = remove_ascii_from_str(s, ch) { + *self = Cow::Owned(result); } - - // Convert to owned and remove - let mut owned = s.to_string(); - owned.remove_all_ascii_in_place(ch); - *self = Cow::Owned(owned); } Cow::Owned(s) => { s.remove_all_ascii_in_place(ch); @@ -137,11 +131,10 @@ impl ReplaceString for Cow<'_, str> { } fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - if from.is_empty() || !self.contains(from) { - return Cow::Borrowed(self); + match replace_str_if_contains(self, from, to) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), } - - Cow::Owned(self.replace(from, to)) } } @@ -192,8 +185,9 @@ mod tests { #[test] fn test_string_replace_all_str() { let s = "hello world hello".to_string(); - let _result = s.replace_all_str("hello", "hi"); - assert_eq!(s, "hi world hi"); + let result = s.replace_all_str("hello", "hi"); + assert_eq!(result, "hi world hi"); + assert_eq!(s, "hello world hello"); // Original string should remain unchanged // Test with no occurrences let s = "hello world".to_string(); @@ -202,6 +196,7 @@ mod tests { Cow::Borrowed(_) => {} Cow::Owned(_) => panic!("Should return borrowed when no changes"), } + assert_eq!(result, "hello world"); assert_eq!(s, "hello world"); } @@ -245,8 +240,9 @@ mod tests { #[test] fn test_cow_replace_all_str() { let s: Cow<'_, str> = Cow::Borrowed("hello world hello"); - let _result = s.replace_all_str("hello", "hi"); - assert_eq!(s, "hi world hi"); + let result = s.replace_all_str("hello", "hi"); + assert_eq!(result, "hi world hi"); + assert_eq!(s, "hello world hello"); // Original string should remain unchanged let s: Cow<'_, str> = Cow::Borrowed("hello world"); let result = s.replace_all_str("xyz", "abc"); @@ -254,6 +250,7 @@ mod tests { Cow::Borrowed(_) => {} Cow::Owned(_) => panic!("Should return borrowed when no changes"), } + assert_eq!(result, "hello world"); assert_eq!(s, "hello world"); } } From d0fd163c244febe042f16f3155e6ee04f8d8a456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:30:36 +0900 Subject: [PATCH 10/13] split --- crates/cow-replace/src/lib.rs | 110 +++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index b916fb5..afca479 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -33,12 +33,28 @@ fn replace_str_if_contains(s: &str, from: &str, to: &str) -> Option { pub trait ReplaceString { fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str>; + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str>; +} +pub trait ReplaceStringInPlace { fn remove_all_ascii_in_place(&mut self, ch: AsciiChar); - fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar); +} - fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str>; +impl ReplaceString for &str { + fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { + match remove_ascii_from_str(self, ch) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), + } + } + + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { + match replace_str_if_contains(self, from, to) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), + } + } } impl ReplaceString for String { @@ -49,6 +65,15 @@ impl ReplaceString for String { } } + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { + match replace_str_if_contains(self, from, to) { + Some(result) => Cow::Owned(result), + None => Cow::Borrowed(self), + } + } +} + +impl ReplaceStringInPlace for String { fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { let target_byte = ch.as_byte(); let bytes = unsafe { self.as_bytes_mut() }; @@ -79,23 +104,25 @@ impl ReplaceString for String { } } } +} - fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - match replace_str_if_contains(self, from, to) { +impl ReplaceString for Cow<'_, str> { + fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { + match remove_ascii_from_str(self, ch) { Some(result) => Cow::Owned(result), None => Cow::Borrowed(self), } } -} -impl ReplaceString for Cow<'_, str> { - fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - match remove_ascii_from_str(self, ch) { + fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { + match replace_str_if_contains(self, from, to) { Some(result) => Cow::Owned(result), None => Cow::Borrowed(self), } } +} +impl ReplaceStringInPlace for Cow<'_, str> { fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { match self { Cow::Borrowed(s) => { @@ -129,13 +156,6 @@ impl ReplaceString for Cow<'_, str> { } } } - - fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - match replace_str_if_contains(self, from, to) { - Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), - } - } } #[cfg(test)] @@ -144,6 +164,38 @@ mod tests { use super::*; + #[test] + fn test_str_remove_all_ascii() { + let s = "hello world"; + let result = s.remove_all_ascii(AsciiChar::l); + assert_eq!(result, "heo word"); + + // Test with no occurrences + let s = "hello world"; + let result = s.remove_all_ascii(AsciiChar::z); + assert_eq!(result, "hello world"); + match result { + Cow::Borrowed(_) => {} + Cow::Owned(_) => panic!("Should return borrowed when no changes"), + } + } + + #[test] + fn test_str_replace_all_str() { + let s = "hello world hello"; + let result = s.replace_all_str("hello", "hi"); + assert_eq!(result, "hi world hi"); + + // Test with no occurrences + let s = "hello world"; + let result = s.replace_all_str("xyz", "abc"); + match result { + Cow::Borrowed(_) => {} + Cow::Owned(_) => panic!("Should return borrowed when no changes"), + } + assert_eq!(result, "hello world"); + } + #[test] fn test_string_remove_all_ascii() { let s = "hello world".to_string(); @@ -253,4 +305,32 @@ mod tests { assert_eq!(result, "hello world"); assert_eq!(s, "hello world"); } + + #[test] + fn test_trait_separation() { + // Test that we can use both traits separately + fn use_replace_string(s: &T) -> Cow<'_, str> { + s.remove_all_ascii(AsciiChar::l) + } + + fn use_replace_string_in_place(s: &mut T) { + s.remove_all_ascii_in_place(AsciiChar::l); + } + + let s1 = "hello world"; + let result = use_replace_string(&s1); + assert_eq!(result, "heo word"); + + let s2 = "hello world".to_string(); + let result = use_replace_string(&s2); + assert_eq!(result, "heo word"); + + let mut s3 = "hello world".to_string(); + use_replace_string_in_place(&mut s3); + assert_eq!(s3, "heo word"); + + let mut s4: Cow<'_, str> = Cow::Borrowed("hello world"); + use_replace_string_in_place(&mut s4); + assert_eq!(s4, "heo word"); + } } From a3657649ab8c00c4141f1e83d8d32cd369df9f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:32:18 +0900 Subject: [PATCH 11/13] doc --- crates/cow-replace/src/lib.rs | 125 ++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index afca479..484a81f 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -31,13 +31,138 @@ fn replace_str_if_contains(s: &str, from: &str, to: &str) -> Option { Some(s.replace(from, to)) } +/// Trait for string replacement operations that return a `Cow`. +/// +/// This trait provides methods for string manipulations that avoid unnecessary +/// allocations when no changes are needed, returning `Cow::Borrowed` for +/// unchanged strings and `Cow::Owned` for modified strings. pub trait ReplaceString { + /// Removes all occurrences of the specified ASCII character from the + /// string. + /// + /// # Arguments + /// + /// * `ch` - The ASCII character to remove from the string + /// + /// # Returns + /// + /// * `Cow::Borrowed` - If the character is not found in the string (no + /// allocation needed) + /// * `Cow::Owned` - If the character is found and removed (new string + /// allocated) + /// + /// # Examples + /// + /// ``` + /// use cow_replace::ReplaceString; + /// use ascii::AsciiChar; + /// use std::borrow::Cow; + /// + /// let text = "hello world"; + /// let result = text.remove_all_ascii(AsciiChar::l); + /// assert_eq!(result, "heo word"); + /// + /// // No allocation when character not found + /// let result = text.remove_all_ascii(AsciiChar::z); + /// match result { + /// Cow::Borrowed(_) => println!("No allocation needed!"), + /// Cow::Owned(_) => unreachable!(), + /// } + /// ``` fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str>; + + /// Replaces all occurrences of a substring with another substring. + /// + /// # Arguments + /// + /// * `from` - The substring to search for and replace + /// * `to` - The replacement substring + /// + /// # Returns + /// + /// * `Cow::Borrowed` - If `from` is not found in the string (no allocation + /// needed) + /// * `Cow::Owned` - If replacements were made (new string allocated) + /// + /// # Examples + /// + /// ``` + /// use cow_replace::ReplaceString; + /// use std::borrow::Cow; + /// + /// let text = "hello world hello"; + /// let result = text.replace_all_str("hello", "hi"); + /// assert_eq!(result, "hi world hi"); + /// + /// // No allocation when substring not found + /// let result = text.replace_all_str("xyz", "abc"); + /// match result { + /// Cow::Borrowed(_) => println!("No allocation needed!"), + /// Cow::Owned(_) => unreachable!(), + /// } + /// ``` fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str>; } +/// Trait for in-place string replacement operations. +/// +/// This trait provides methods that modify the string directly without creating +/// new allocations when possible. These operations are more memory-efficient +/// but modify the original string. pub trait ReplaceStringInPlace { + /// Removes all occurrences of the specified ASCII character from the string + /// in-place. + /// + /// This method modifies the string directly, potentially reducing its + /// length. For `Cow`, this may convert a borrowed string to an + /// owned string if modifications are needed. + /// + /// # Arguments + /// + /// * `ch` - The ASCII character to remove from the string + /// + /// # Examples + /// + /// ``` + /// use cow_replace::ReplaceStringInPlace; + /// use ascii::AsciiChar; + /// + /// let mut text = "hello world".to_string(); + /// text.remove_all_ascii_in_place(AsciiChar::l); + /// assert_eq!(text, "heo word"); + /// + /// // Works with empty results too + /// let mut text = "lllll".to_string(); + /// text.remove_all_ascii_in_place(AsciiChar::l); + /// assert_eq!(text, ""); + /// ``` fn remove_all_ascii_in_place(&mut self, ch: AsciiChar); + + /// Replaces all occurrences of one ASCII character with another in-place. + /// + /// This method modifies the string directly by replacing bytes. Since both + /// characters are ASCII, the string length remains the same. + /// + /// # Arguments + /// + /// * `from` - The ASCII character to search for and replace + /// * `to` - The ASCII character to replace with + /// + /// # Examples + /// + /// ``` + /// use cow_replace::ReplaceStringInPlace; + /// use ascii::AsciiChar; + /// + /// let mut text = "hello world".to_string(); + /// text.replace_all_ascii_in_place(AsciiChar::l, AsciiChar::x); + /// assert_eq!(text, "hexxo worxd"); + /// + /// // No change if character not found + /// let mut text = "hello world".to_string(); + /// text.replace_all_ascii_in_place(AsciiChar::z, AsciiChar::x); + /// assert_eq!(text, "hello world"); + /// ``` fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar); } From 75e7d522c388bc44c92a50e1a45d5fd4f68d568d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Sun, 22 Jun 2025 07:33:42 +0900 Subject: [PATCH 12/13] Update crates/cow-replace/src/lib.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/cow-replace/src/lib.rs | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index 484a81f..672ac38 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -166,38 +166,21 @@ pub trait ReplaceStringInPlace { fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar); } -impl ReplaceString for &str { +impl> ReplaceString for T { fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - match remove_ascii_from_str(self, ch) { - Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), - } - } - - fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - match replace_str_if_contains(self, from, to) { - Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), - } - } -} - -impl ReplaceString for String { - fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - match remove_ascii_from_str(self, ch) { + match remove_ascii_from_str(self.as_ref(), ch) { Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), + None => Cow::Borrowed(self.as_ref()), } } fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - match replace_str_if_contains(self, from, to) { + match replace_str_if_contains(self.as_ref(), from, to) { Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), + None => Cow::Borrowed(self.as_ref()), } } } - impl ReplaceStringInPlace for String { fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { let target_byte = ch.as_byte(); From 60293e52c00e030d82f932c34d4ae6fa55c84dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4=20=28Donny=29?= Date: Sun, 22 Jun 2025 07:33:59 +0900 Subject: [PATCH 13/13] review --- crates/cow-replace/src/lib.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/cow-replace/src/lib.rs b/crates/cow-replace/src/lib.rs index 672ac38..966bc29 100644 --- a/crates/cow-replace/src/lib.rs +++ b/crates/cow-replace/src/lib.rs @@ -214,22 +214,6 @@ impl ReplaceStringInPlace for String { } } -impl ReplaceString for Cow<'_, str> { - fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> { - match remove_ascii_from_str(self, ch) { - Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), - } - } - - fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> { - match replace_str_if_contains(self, from, to) { - Some(result) => Cow::Owned(result), - None => Cow::Borrowed(self), - } - } -} - impl ReplaceStringInPlace for Cow<'_, str> { fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) { match self {