From 1ffdb043a30de5d731056230aefff1e5789cefa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:41:32 +0000 Subject: [PATCH 1/2] Initial plan From cb2f5b0a2b8791156205c44a00ca0838a368275f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:45:16 +0000 Subject: [PATCH 2/2] feat: add core and feature-gated string conversion impls --- Cargo.lock | 55 +++++++++++++++++++++ Cargo.toml | 4 ++ README.md | 6 +++ src/cow_str.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++ src/inline_str.rs | 85 +++++++++++++++++++++++++++++++- src/lib.rs | 2 + 6 files changed, 273 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0bfa84c..2697495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,35 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -49,9 +78,11 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" name = "moos" version = "0.3.0" dependencies = [ + "compact_str", "derive_more", "serde", "serde_json", + "smol_str", ] [[package]] @@ -81,6 +112,18 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "semver" version = "1.0.27" @@ -130,6 +173,18 @@ dependencies = [ "zmij", ] +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.111" diff --git a/Cargo.toml b/Cargo.toml index d702aa9..ba4c900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ std = ["serde?/std"] is_variant = ["derive_more/is_variant"] constructors = ["derive_more/constructor"] index = ["derive_more/index", "derive_more/index_mut"] +smol_str = ["dep:smol_str"] +compact_str = ["dep:compact_str"] [dependencies] derive_more = { version = "2.1", default-features = false, optional = true } @@ -27,6 +29,8 @@ serde = { version = "1.0", features = [ "rc", "alloc", ], default-features = false, optional = true } +smol_str = { version = "0.3", default-features = false, optional = true } +compact_str = { version = "0.9", default-features = false, optional = true } [dev-dependencies] serde_json = "1.0" diff --git a/README.md b/README.md index 90bfdbb..939fd83 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ usage is a concern, such as embedded systems or real-time applications. - [x] Supports comparison and ordering operations. - [x] Supports serialization/deserialization with **[serde]** > **Note**: Requires the `serde` feature flag to be enabled. +- [x] Supports conversions with **[smol_str]** + > **Note**: Requires the `smol_str` feature flag to be enabled. +- [x] Supports conversions with **[compact_str]** + > **Note**: Requires the `compact_str` feature flag to be enabled. ```rust use moos::InlineStr; @@ -191,6 +195,8 @@ match InlineStr::try_from(long_string) { [Nicholas Berlette]: https://github.com/nberlette "Follow @nberlette on GitHub for more cool stuff!" [`MAX_INLINE_STR_LEN`]: #max_inline_str_len [serde]: https://crates.io/crates/serde "Serialization framework for Rust" +[smol_str]: https://crates.io/crates/smol_str "Small-string optimization string type" +[compact_str]: https://crates.io/crates/compact_str "Compact heap-avoiding string type" [moos]: https://crates.io/crates/moos "moos on crates.io" [GitHub]: https://github.com/nberlette/moos "moos on GitHub" [Issues]: https://github.com/nberlette/moos/issues "moos issues on GitHub" diff --git a/src/cow_str.rs b/src/cow_str.rs index 7d7db99..c384866 100644 --- a/src/cow_str.rs +++ b/src/cow_str.rs @@ -236,6 +236,13 @@ impl<'i> AsRef for CowStr<'i> { } } +impl<'i> AsRef<[u8]> for CowStr<'i> { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + impl<'i> AsMut for CowStr<'i> { #[inline(always)] fn as_mut(&mut self) -> &mut str { @@ -367,6 +374,20 @@ impl<'i> From for CowStr<'i> { } } +impl<'i> From> for CowStr<'i> { + #[inline(always)] + fn from(s: Box) -> Self { + CowStr::Owned(s) + } +} + +impl<'i> From<&'i String> for CowStr<'i> { + #[inline(always)] + fn from(s: &'i String) -> Self { + CowStr::Borrowed(s.as_str()) + } +} + impl<'i> From for CowStr<'i> { #[inline(always)] fn from(c: char) -> Self { @@ -374,6 +395,13 @@ impl<'i> From for CowStr<'i> { } } +impl<'i> From for CowStr<'i> { + #[inline(always)] + fn from(s: InlineStr) -> Self { + CowStr::Inlined(s) + } +} + impl<'i> From> for CowStr<'i> { #[inline(always)] fn from(s: Cow<'i, str>) -> Self { @@ -409,6 +437,62 @@ impl<'i> From> for String { } } +#[cfg(feature = "smol_str")] +mod smol_str_impl { + use smol_str::SmolStr; + + use super::*; + + impl<'i> From for CowStr<'i> { + #[inline(always)] + fn from(s: SmolStr) -> Self { + CowStr::Owned(s.to_string().into_boxed_str()) + } + } + + impl<'i> From<&'i SmolStr> for CowStr<'i> { + #[inline(always)] + fn from(s: &'i SmolStr) -> Self { + CowStr::Borrowed(s.as_str()) + } + } + + impl<'i> From> for SmolStr { + #[inline(always)] + fn from(s: CowStr<'i>) -> Self { + SmolStr::new(s.as_str()) + } + } +} + +#[cfg(feature = "compact_str")] +mod compact_str_impl { + use compact_str::CompactString; + + use super::*; + + impl<'i> From for CowStr<'i> { + #[inline(always)] + fn from(s: CompactString) -> Self { + CowStr::Owned(s.to_string().into_boxed_str()) + } + } + + impl<'i> From<&'i CompactString> for CowStr<'i> { + #[inline(always)] + fn from(s: &'i CompactString) -> Self { + CowStr::Borrowed(s.as_str()) + } + } + + impl<'i> From> for CompactString { + #[inline(always)] + fn from(s: CowStr<'i>) -> Self { + CompactString::from(s.as_str()) + } + } +} + #[cfg(not(feature = "is_variant"))] impl<'i> CowStr<'i> { /// Returns `true` if the `CowStr` is the `Owned` variant. @@ -660,6 +744,44 @@ mod tests { assert!(variant_eq(&actual, &expected)); } + #[test] + fn from_core_types() { + let boxed: Box = "boxed".to_string().into_boxed_str(); + let cow = CowStr::from(boxed); + assert!(cow.is_owned()); + assert_eq!(cow.as_str(), "boxed"); + + let s = "string".to_string(); + let cow = CowStr::from(&s); + assert!(cow.is_borrowed()); + assert_eq!(cow.as_str(), "string"); + + let inline = InlineStr::try_from("inline").unwrap(); + let cow = CowStr::from(inline); + assert!(cow.is_inlined()); + assert_eq!(cow.as_str(), "inline"); + } + + #[cfg(feature = "smol_str")] + #[test] + fn smol_str_conversions() { + let smol = smol_str::SmolStr::new("smol"); + let cow = CowStr::from(smol.clone()); + assert_eq!(cow.as_str(), "smol"); + let smol_back = smol_str::SmolStr::from(cow); + assert_eq!(smol_back, smol); + } + + #[cfg(feature = "compact_str")] + #[test] + fn compact_str_conversions() { + let compact = compact_str::CompactString::from("compact"); + let cow = CowStr::from(compact.clone()); + assert_eq!(cow.as_str(), "compact"); + let compact_back = compact_str::CompactString::from(cow); + assert_eq!(compact_back, compact); + } + fn variant_eq(a: &T, b: &T) -> bool { std::mem::discriminant(a) == std::mem::discriminant(b) } diff --git a/src/inline_str.rs b/src/inline_str.rs index cef4d20..44923a5 100644 --- a/src/inline_str.rs +++ b/src/inline_str.rs @@ -76,7 +76,7 @@ pub struct StringTooLongError; /// /// # fn main() -> Result<(), StringTooLongError> { /// let inline_str: InlineStr = "Hello".parse()?; -/// assert_eq!(inline_str.as_ref(), "Hello"); +/// assert_eq!(inline_str.as_str(), "Hello"); /// assert_eq!(inline_str.len(), 5); /// /// // This will fail because the string is too long: @@ -224,6 +224,13 @@ impl AsRef for InlineStr { } } +impl AsRef<[u8]> for InlineStr { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + impl AsMut for InlineStr { #[inline(always)] fn as_mut(&mut self) -> &mut str { @@ -296,6 +303,13 @@ impl From for InlineStr { } } +impl From> for InlineStr { + #[inline(always)] + fn from(s: CowStr<'_>) -> Self { + InlineStr::from(s.into_string()) + } +} + impl TryFrom<&str> for InlineStr { type Error = StringTooLongError; @@ -312,6 +326,48 @@ impl TryFrom<&str> for InlineStr { } } +#[cfg(feature = "smol_str")] +mod smol_str_impl { + use smol_str::SmolStr; + + use super::*; + + impl From for InlineStr { + #[inline(always)] + fn from(s: SmolStr) -> Self { + InlineStr::from(s.as_str().to_string()) + } + } + + impl From for SmolStr { + #[inline(always)] + fn from(s: InlineStr) -> Self { + SmolStr::new(s.as_str()) + } + } +} + +#[cfg(feature = "compact_str")] +mod compact_str_impl { + use compact_str::CompactString; + + use super::*; + + impl From for InlineStr { + #[inline(always)] + fn from(s: CompactString) -> Self { + InlineStr::from(s.to_string()) + } + } + + impl From for CompactString { + #[inline(always)] + fn from(s: InlineStr) -> Self { + CompactString::from(s.as_str()) + } + } +} + impl Hash for InlineStr { #[inline(always)] fn hash(&self, state: &mut H) { @@ -640,4 +696,31 @@ mod tests { } assert_eq!(s, "HELLO"); } + + #[test] + fn inline_str_from_cow_str() { + let cow = CowStr::Borrowed("abcdef"); + let inline = InlineStr::from(cow); + assert_eq!(inline, "abcdef"); + } + + #[cfg(feature = "smol_str")] + #[test] + fn smol_str_conversions() { + let smol = smol_str::SmolStr::new("smol"); + let inline = InlineStr::from(smol.clone()); + assert_eq!(inline, "smol"); + let smol_back = smol_str::SmolStr::from(inline); + assert_eq!(smol_back, smol); + } + + #[cfg(feature = "compact_str")] + #[test] + fn compact_str_conversions() { + let compact = compact_str::CompactString::from("compact"); + let inline = InlineStr::from(compact.clone()); + assert_eq!(inline, "compact"); + let compact_back = compact_str::CompactString::from(inline); + assert_eq!(compact_back, compact); + } } diff --git a/src/lib.rs b/src/lib.rs index 684c5a6..adc93bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,8 @@ //! - `std`: Enables integration with the Rust standard library. When disabled, //! which is the default, the crate operates in `no_std` mode. //! - `serde`†: Enables serialization and deserialization support via Serde. +//! - `smol_str`: Adds conversions to/from [`smol_str::SmolStr`]. +//! - `compact_str`: Adds conversions to/from [`compact_str::CompactString`]. //! //! > † enabled by default