From 55140dd790dba27f96aa6ce3d8bb758256ae545c Mon Sep 17 00:00:00 2001 From: Thomas Pendock Date: Wed, 25 Feb 2026 18:15:54 -0800 Subject: [PATCH 1/3] default impl --- cold-string/src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cold-string/src/lib.rs b/cold-string/src/lib.rs index 1f356a5..bcaa1e7 100644 --- a/cold-string/src/lib.rs +++ b/cold-string/src/lib.rs @@ -346,6 +346,25 @@ impl ColdString { pub fn as_str(&self) -> &str { unsafe { str::from_utf8_unchecked(self.as_bytes()) } } + + /// Returns `true` if this `ColdString` has a length of zero, and `false` otherwise. + /// + /// # Examples + /// + /// ``` + /// let v = cold_string::ColdString::new(""); + /// assert!(v.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for ColdString { + fn default() -> Self { + Self::new_inline("") + } } impl Deref for ColdString { @@ -542,6 +561,14 @@ mod tests { assert_eq!(mem::align_of::(), 1); } + #[test] + fn test_default() { + assert!(ColdString::default().is_empty()); + assert_eq!(ColdString::default().len(), 0); + assert_eq!(ColdString::default(), ""); + assert_eq!(ColdString::default(), ColdString::new("")); + } + #[test] fn it_works() { for s in [ From fae2c59e5b3a80d5a5eed1e36d1c2c70493590b2 Mon Sep 17 00:00:00 2001 From: Thomas Pendock Date: Wed, 25 Feb 2026 18:19:02 -0800 Subject: [PATCH 2/3] ord + partial ord impl --- cold-string/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cold-string/src/lib.rs b/cold-string/src/lib.rs index bcaa1e7..8b9929e 100644 --- a/cold-string/src/lib.rs +++ b/cold-string/src/lib.rs @@ -20,6 +20,7 @@ use core::{ mem, ops::Deref, ptr, slice, str, + cmp::Ordering, }; mod vint; @@ -501,6 +502,18 @@ impl AsRef<[u8]> for ColdString { } } +impl Ord for ColdString { + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialOrd for ColdString { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl alloc::str::FromStr for ColdString { type Err = core::convert::Infallible; fn from_str(s: &str) -> Result { From 0a672e0fcd3615534f591ed2863ef15ee70d0fbd Mon Sep 17 00:00:00 2001 From: Thomas Pendock Date: Wed, 25 Feb 2026 18:59:29 -0800 Subject: [PATCH 3/3] More tests --- .github/workflows/rust.yml | 18 +++++---- cold-string/src/lib.rs | 73 +++++++++++++++++++++++++++-------- cold-string/tests/property.rs | 2 +- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9a1fb77..057dcf4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -23,16 +23,18 @@ jobs: - name: Check run: cargo hack check --manifest-path cold-string/Cargo.toml --verbose --feature-powerset --version-range 1.60.. - name: Test No Exposed Provenance - run: cargo +1.74 hack test --manifest-path cold-string/Cargo.toml --verbose --release --feature-powerset + run: cargo +1.74 hack test --manifest-path cold-string/Cargo.toml --verbose --feature-powerset - name: Tests - run: cargo hack test --manifest-path cold-string/Cargo.toml --verbose --release --feature-powerset + run: cargo hack test --manifest-path cold-string/Cargo.toml --verbose --feature-powerset - name: Install nightly + Miri run: | rustup toolchain install nightly rustup component add miri --toolchain nightly - - name: Run Miri - run: | - cargo +nightly miri test --manifest-path cold-string/Cargo.toml - - name: Run Miri Unknown - run: | - cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target mips-unknown-linux-gnu + - name: Miri 64 bit LE + run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml + - name: Miri 64 bit BE + run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target powerpc64-unknown-linux-gnu + - name: Miri 32 bit LE + run: cargo +nightly miri test --target i686-unknown-linux-gnu + - name: Miri 32 bit BE + run: cargo +nightly miri test --manifest-path cold-string/Cargo.toml --target mips-unknown-linux-gnu diff --git a/cold-string/src/lib.rs b/cold-string/src/lib.rs index 8b9929e..0695997 100644 --- a/cold-string/src/lib.rs +++ b/cold-string/src/lib.rs @@ -582,6 +582,25 @@ mod tests { assert_eq!(ColdString::default(), ColdString::new("")); } + fn assert_correct(s: &str) { + let cs = ColdString::new(s); + assert_eq!(s.len() <= mem::size_of::(), cs.is_inline()); + assert_eq!(cs.len(), s.len()); + assert_eq!(cs.as_bytes(), s.as_bytes()); + assert_eq!(cs.as_str(), s); + assert_eq!(cs.clone(), cs); + let bh = DefaultHashBuilder::new(); + let mut hasher1 = bh.build_hasher(); + cs.hash(&mut hasher1); + let mut hasher2 = bh.build_hasher(); + cs.clone().hash(&mut hasher2); + assert_eq!(hasher1.finish(), hasher2.finish()); + assert_eq!(cs, s); + assert_eq!(s, cs); + assert_eq!(cs, *s); + assert_eq!(*s, cs); + } + #[test] fn it_works() { for s in [ @@ -616,22 +635,44 @@ mod tests { "AaAa0 ® ", str::from_utf8(&[240, 158, 186, 128, 240, 145, 143, 151]).unwrap(), ] { - let cs = ColdString::new(s); - assert_eq!(s.len() <= mem::size_of::(), cs.is_inline()); - assert_eq!(cs.len(), s.len()); - assert_eq!(cs.as_bytes(), s.as_bytes()); - assert_eq!(cs.as_str(), s); - assert_eq!(cs.clone(), cs); - let bh = DefaultHashBuilder::new(); - let mut hasher1 = bh.build_hasher(); - cs.hash(&mut hasher1); - let mut hasher2 = bh.build_hasher(); - cs.clone().hash(&mut hasher2); - assert_eq!(hasher1.finish(), hasher2.finish()); - assert_eq!(cs, s); - assert_eq!(s, cs); - assert_eq!(cs, *s); - assert_eq!(*s, cs); + assert_correct(s); + } + } + + fn char_from_leading_byte(b: u8) -> Option { + match b { + 0x00..=0x7F => Some(b as char), + 0xC2..=0xDF => str::from_utf8(&[b, 0x91]).unwrap().chars().next(), + 0xE0 => str::from_utf8(&[b, 0xA0, 0x91]).unwrap().chars().next(), + 0xE1..=0xEC | 0xEE..=0xEF => str::from_utf8(&[b, 0x91, 0xA5]).unwrap().chars().next(), + 0xED => str::from_utf8(&[b, 0x80, 0x91]).unwrap().chars().next(), + 0xF0 => str::from_utf8(&[b, 0x90, 0x91, 0xA5]).unwrap().chars().next(), + 0xF1..=0xF3 => str::from_utf8(&[b, 0x91, 0xA5, 0x82]).unwrap().chars().next(), + 0xF4 => str::from_utf8(&[b, 0x80, 0x91, 0x82]).unwrap().chars().next(), + _ => None, + } + } + + #[test] + fn test_edges() { + let width = mem::size_of::(); + for len in [width - 1, width, width + 1] { + for first_byte in 0u8..=255 { + let first_char = match char_from_leading_byte(first_byte) { + Some(c) => c, + None => continue, + }; + + let mut s = String::with_capacity(len); + s.push(first_char); + + while s.len() < len { + let c = core::char::from_digit((len - s.len()) as u32, 10).unwrap(); + s.push(c); + } + + assert_correct(&s); + } } } diff --git a/cold-string/tests/property.rs b/cold-string/tests/property.rs index ec64c2a..231edf4 100644 --- a/cold-string/tests/property.rs +++ b/cold-string/tests/property.rs @@ -12,7 +12,7 @@ fn proptest_config() -> ProptestConfig { #[cfg(not(miri))] fn proptest_config() -> ProptestConfig { - ProptestConfig::with_cases(131072) + ProptestConfig::with_cases(65536) } proptest! {