waterui-str
A memory-efficient, reference-counted string type optimized for both static and owned strings.
Overview
waterui-str provides Str, a hybrid string type that automatically chooses between static string references and reference-counted owned strings. This design eliminates unnecessary allocations for static strings while enabling efficient cloning for owned strings through reference counting.
The crate is designed for no_std environments (with alloc), making it suitable for embedded systems and WebAssembly targets. It integrates seamlessly with WaterUI's reactive system through the nami-core integration.
Key features:
- Zero-cost static strings: Static string literals stored as pointers without allocation
- Reference-counted owned strings: Efficient cloning through internal reference counting
- Transparent API: Derefs to
&str, works with all standard string operations - Reactive integration: Compatible with
WaterUI'snamireactive primitives viaimpl_constant! - Optional serde support: Serialize and deserialize with the
serdefeature
Installation
Add to your Cargo.toml:
[]
= "0.2"
With serde support:
[]
= { = "0.2", = ["serde"] }
Quick Start
use Str;
// Static strings - no allocation
let static_str = from;
// Owned strings - reference counted
let owned = from;
// Cheap cloning
let clone = owned.clone; // Just increments ref count
// Transparent string operations
assert_eq!;
assert!;
// Concatenation
let combined = static_str + " " + &owned;
assert_eq!;
Core Concepts
Internal Representation
Str uses a clever tagged pointer representation:
- Positive length: Points to static string data (
&'static str) - Negative length: Points to
Shared(reference-countedString)
This allows zero-overhead discrimination between static and owned strings at runtime.
Reference Counting
Owned strings use internal reference counting via Shared:
- Clone operations increment the reference count
- Drop operations decrement the count and free memory when reaching zero
- Reference counts are intentionally not exposed in the public API
Memory Optimization
Empty strings always use a static empty string reference, regardless of how they're created:
use Str;
let empty1 = new;
let empty2 = from;
let empty3 = from;
// All three use the same static "" reference
Examples
Creating Strings
use Str;
// From static string literal
let s1 = from;
// From owned String
let s2 = from;
// From UTF-8 bytes
let bytes = vec!; // "hello"
let s3 = from_utf8.unwrap;
// Empty string
let s4 = new;
String Manipulation
use Str;
let mut s = from;
s.append;
assert_eq!;
// Concatenation with +
let s1 = from;
let s2 = s1 + "bar";
assert_eq!;
// AddAssign
let mut s3 = from;
s3 += " world";
assert_eq!;
Iteration and Collection
use Str;
// Collect from iterator
let words = vec!;
let s: Str = words.into_iter.collect;
assert_eq!;
// Extend
let mut s = from;
s.extend;
assert_eq!;
Conversion to String
use Str;
let s1 = from;
let s2 = s1.clone;
// Convert to String - takes ownership if ref count is 1
let string1 = s1.into_string; // Copies because s2 still exists
assert_eq!;
// s2 is now the only reference
let string2 = s2.into_string; // No copy, takes ownership
assert_eq!;
API Overview
Construction
Str::new()- Create empty stringStr::from_static(&'static str)- Create from static string literalStr::from_utf8(Vec<u8>)- Create from UTF-8 bytes with validationunsafe Str::from_utf8_unchecked(Vec<u8>)- Create from UTF-8 bytes without validation
Inspection
as_str(&self) -> &str- Get string slicelen(&self) -> usize- Get byte lengthis_empty(&self) -> bool- Check if empty
Modification
append(&mut self, &str)- Append stringinto_string(self) -> String- Convert to owned String
Traits Implemented
Deref<Target = str>- Transparent access to string methodsClone- Efficient reference-counted cloningDefault- Empty stringDisplay,Debug- FormattingHash,Eq,Ord- Collections and comparisonsAsRef<str>,AsRef<[u8]>,Borrow<str>- ConversionsFromStr,FromIterator- Parsing and collectionAdd,AddAssign- ConcatenationExtend- Extension from iteratorsIndex<I>- Slice indexing
Standard Library Integration (when std is available)
AsRef<OsStr>,AsRef<Path>- Filesystem operationsTryFrom<OsString>- OS string conversionToSocketAddrs- Network address resolution
Design Rationale
Why Not Cow<'static, str>?
While Cow<'static, str> provides similar functionality, Str offers:
- Better clone performance: Reference counting vs. full string copy for
Cow::Owned - Smaller size: Single pointer + length vs. discriminant + pointer + length
- Specialized API: Methods like
append()optimized for the use case
Why Hide Reference Counts?
The internal reference count is deliberately not exposed in the public API. This:
- Prevents code from relying on reference count values
- Allows future optimization changes without breaking the API
- Encourages treating
Stras a simple value type
Memory Safety
The crate includes extensive memory safety tests designed for Miri (Rust's undefined behavior detector), covering:
- Clone/drop cycle patterns
- Interleaved operations
- Reference counting edge cases
- Pointer stability guarantees
- Large string handling
- Concurrent-like access patterns (single-threaded stress tests)
Performance Characteristics
- Static strings: Zero allocation, zero cost to clone
- Owned strings: Single allocation, O(1) clone (ref count increment)
- Deref operations: Zero cost - compiles to a pointer dereference
into_string()with unique ownership: Zero copy, takes ownershipinto_string()with shared ownership: Single allocation and copy
License
Licensed under the same terms as the WaterUI project.