From ada6d9581ebe4393c3316c8f6a040b0e6501a256 Mon Sep 17 00:00:00 2001 From: mxsm Date: Sat, 20 Jun 2026 10:46:43 +0800 Subject: [PATCH] Feat: prepare v1.2.0 architecture convergence --- .github/workflows/release.yml | 2 +- Cargo.toml | 2 +- README.md | 21 ++++++++++++++------- bench-results/README.md | 2 +- src/cheetah_string.rs | 24 +++++++++++++++++++++++- src/lib.rs | 4 +++- tests/mutation.rs | 23 +++++++++++++++++++++++ 7 files changed, 66 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3f5beb..2d1c2fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: inputs: version: - description: "Version to release, for example 1.1.0" + description: "Version to release, for example 1.2.0" required: true type: string diff --git a/Cargo.toml b/Cargo.toml index e490fa3..1d56b1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cheetah-string" -version = "1.1.0" +version = "1.2.0" authors = ["mxsm "] edition = "2021" homepage = "https://github.com/mxsm/cheetah-string" diff --git a/README.md b/README.md index 713362b..79276d2 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,14 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -cheetah-string = "1.1.0" +cheetah-string = "1.2.0" ``` ### Optional Features ```toml [dependencies] -cheetah-string = { version = "1.1.0", features = ["bytes", "serde", "simd"] } +cheetah-string = { version = "1.2.0", features = ["bytes", "serde", "simd"] } ``` Available features: @@ -61,6 +61,7 @@ Available features: - `bytes`: `CheetahBytes` and integration with the `bytes` crate - `serde`: Serialization support via serde - `simd`: SIMD-accelerated string operations (x86_64 SSE2) +- `experimental-packed`: Experimental packed representation prototype ## 🚀 Quick Start @@ -94,6 +95,11 @@ builder.push_str("Hello"); builder.push_str(", "); builder.push_str("World!"); +// Explicit String storage policy +let mut owned = CheetahString::from_string_owned(String::with_capacity(128)); +owned.push_str("capacity-preserving"); +let shared = CheetahString::from_string_shared("clone-cheap".repeat(16)); + // Safe UTF-8 validation let bytes = b"hello"; let s = CheetahString::try_from_bytes(bytes).unwrap(); @@ -125,10 +131,9 @@ CheetahString intelligently chooses the most efficient storage: |-------------|---------|------------------|----------| | ≤ 23 bytes | Inline (SSO) | 0 | Short strings, identifiers | | Static | `&'static str` | 0 | String literals | -| Dynamic | `Arc` | 1 | Long immutable strings, shared data | -| Builder | `String` | 1 | Reserved capacity, repeated mutation | -| From Arc | `Arc` | 1 | Interop with existing Arc | -| Bytes | `bytes::Bytes` | 1 | Network buffers (with feature) | +| Shared | `Arc` | 1 | Long immutable strings, shared data | +| Owned | `String` | 1 | Reserved capacity, repeated mutation | +| Bytes | `CheetahBytes` | 1 | Byte-oriented network buffers (with feature) | ## 🔧 API Overview @@ -136,7 +141,9 @@ CheetahString intelligently chooses the most efficient storage: - `new()`, `empty()`, `default()` - Create empty strings - `from(s)` - From `&str`, `String`, `&String`, `char`, etc. - `from_static_str(s)` - Zero-cost wrapper for `'static str` -- `from_string(s)` - From owned `String` +- `from_string(s)` - From owned `String` with the 1.x shared-long-string policy +- `from_string_owned(s)` - Preserve `String` ownership and spare capacity for mutation +- `from_string_shared(s)` - Convert long owned strings to clone-cheap shared storage - `try_from_bytes(b)` - Safe construction from bytes with UTF-8 validation - `CheetahBytes` - Byte-oriented companion type available with the `bytes` feature - `with_capacity(n)` - Pre-allocate capacity diff --git a/bench-results/README.md b/bench-results/README.md index 1e0fc59..7891693 100644 --- a/bench-results/README.md +++ b/bench-results/README.md @@ -30,7 +30,7 @@ Minimum metadata for generated JSON artifacts: ```json { "crate": "cheetah-string", - "version": "1.1.0", + "version": "1.2.0", "profile": "release", "target": "x86_64-unknown-linux-gnu", "rustc": "rustc 1.xx.x", diff --git a/src/cheetah_string.rs b/src/cheetah_string.rs index 79d9890..e16b940 100644 --- a/src/cheetah_string.rs +++ b/src/cheetah_string.rs @@ -410,6 +410,28 @@ impl CheetahString { #[inline] pub fn from_string(s: String) -> Self { + CheetahString::from_string_shared(s) + } + + /// Creates a `CheetahString` from an owned `String` while preserving + /// ownership and spare capacity for later mutation. + /// + /// This constructor is intended for builder-style paths that will continue + /// appending to the string. It keeps long strings in owned storage instead + /// of converting them to shared storage. + #[inline] + pub fn from_string_owned(s: String) -> Self { + CheetahString::from_builder_string(s) + } + + /// Creates a `CheetahString` from an owned `String` using shared storage + /// for long immutable strings. + /// + /// This is the same storage policy used by `from_string` in the 1.x line. + /// Use `from_string_owned` when spare capacity should be preserved for + /// later mutation. + #[inline] + pub fn from_string_shared(s: String) -> Self { if s.len() <= INLINE_CAPACITY { // Use inline storage for short strings let mut data = [0u8; INLINE_CAPACITY]; @@ -1206,7 +1228,7 @@ impl Add for CheetahString { #[inline] fn add(mut self, rhs: String) -> Self::Output { if self.is_empty() { - return CheetahString::from_string(rhs); + return CheetahString::from_string_owned(rhs); } self.push_str_internal(&rhs); diff --git a/src/lib.rs b/src/lib.rs index bdd5f02..96dc635 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ //! It is usable in both `std` and `no_std` environments. Additionally, CheetahString supports serde for serialization and deserialization. //! The `bytes` feature exposes `CheetahBytes` for byte-oriented data. //! It minimizes allocations across small, shared, and builder-oriented string workloads. +//! The `from_string_owned` and `from_string_shared` constructors make owned +//! mutation and clone-cheap sharing policies explicit. //! Substring search uses `memchr`/`memmem` by default. //! //! # SIMD Acceleration @@ -21,7 +23,7 @@ //! To enable SIMD acceleration: //! ```toml //! [dependencies] -//! cheetah-string = { version = "1.1.0", features = ["simd"] } +//! cheetah-string = { version = "1.2.0", features = ["simd"] } //! ``` //! //! # Examples diff --git a/tests/mutation.rs b/tests/mutation.rs index 2e3ab77..35cd414 100644 --- a/tests/mutation.rs +++ b/tests/mutation.rs @@ -35,6 +35,29 @@ fn add_reuses_owned_lhs_capacity() { assert_eq!(s.as_bytes().as_ptr(), before); } +#[test] +fn from_string_owned_preserves_spare_capacity_for_push() { + let mut raw = String::with_capacity(128); + raw.push_str("hello"); + + let mut s = CheetahString::from_string_owned(raw); + let before = s.as_bytes().as_ptr(); + + s.push_str(" world"); + + assert_eq!(s, "hello world"); + assert_eq!(s.as_bytes().as_ptr(), before); +} + +#[test] +fn from_string_shared_keeps_long_immutable_inputs_clone_cheap() { + let s = CheetahString::from_string_shared("a".repeat(128)); + let cloned = s.clone(); + + assert_eq!(s.as_str(), cloned.as_str()); + assert_eq!(s.as_bytes().as_ptr(), cloned.as_bytes().as_ptr()); +} + #[test] fn reserve_zero_keeps_existing_buffer() { let mut s = CheetahString::with_capacity(128);