diff --git a/Cargo.lock b/Cargo.lock index cf41839..c8d3b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ [[package]] name = "bit-string" -version = "0.4.2" +version = "0.4.3" dependencies = [ "bitvec_simd", "codspeed-divan-compat", diff --git a/Cargo.toml b/Cargo.toml index 1dbdc7a..b22c123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bit-string" -version = "0.4.2" +version = "0.4.3" edition = "2024" description = "A compact owned bit string type with editing, matching, and bitwise operations." readme = "README.md" diff --git a/README.md b/README.md index 245abbd..cd7024f 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,28 @@ [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/gh/jcfangc/bit-string) [![Coverage](https://codecov.io/gh/jcfangc/bit-string/branch/main/graph/badge.svg)](https://codecov.io/gh/jcfangc/bit-string) -A `no_std` Rust crate providing a compact owned bit string type with construction, editing, matching, and bitwise operations. +A `no_std` Rust crate providing a compact owned bit string type and a zero-copy view, with construction, editing, matching, comparison, hashing, and bitwise operations. -The core type is `BitString`. Bits are packed into `Box<[u64]>` with unused high bits in the last word always zero (masked after every mutation). +The two core types mirror the `String`/`&str` relationship: + +| Type | Role | Size | `Copy` | +|------|------|------|--------| +| `BitString` | Owned bit string, backed by `Box<[u64]>` | 4×usize | No | +| `BitStr<'bs>` | Zero-copy borrowed view of a `BitString` or subrange | 3×usize (24 bytes) | **Yes** | + +Bits are packed little-endian into `u64` words. Unused high bits in the last word are always zero — enforced after every mutation on `BitString` and never observable through `BitStr`. ## Features - **Construction**: `new`, `zeros`, `repeat`, `from_bool_iter`, `from_words`, `try_from(&str)` +- **Zero-copy view**: `BitStr<'bs>` — 24-byte `Copy` type returned by `as_bit_str()`, `slice()`, `slice_from()`, `slice_until()` +- **Conversion**: `to_bit_string()` — copies a `BitStr` view into an owned `BitString` - **Bitwise ops**: `and`, `or`, `xor`, `not`, `shl`, `shr` (each with `_assign` and `_into` variants) - **Bit counting**: `count_ones`, `count_zeros` - **Editing**: `push`, `pop`, `insert`, `remove`, `set`, `extend`, `truncate`, `slice`, `split_off`, `replace_interval`, `retain`, `push_bit_string`, `insert_bit_string` - **Matching**: `starts_with`, `ends_with`, `contains`, `find`, `rfind`, `strip_prefix`, `strip_suffix` -- **Access**: `get`, `len`, `is_empty`, `as_words`, `get_chunk`, `to_string`, `iter` +- **Comparison & hashing**: `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash` for both `BitStr` and `BitString` — lexicographic comparison with SIMD acceleration +- **Access**: `get`, `first`, `last`, `get_chunk`, `len`/`bit_len`, `is_empty`, `words`, `iter` ### SIMD backends @@ -40,10 +50,25 @@ use bit_string::BitString; let a = BitString::try_from("1010").unwrap(); let b = BitString::try_from("1100").unwrap(); +// Bitwise operations assert_eq!(a.and(&b).unwrap().to_string(), "1000"); assert_eq!(a.or(&b).unwrap().to_string(), "1110"); assert_eq!((!a).to_string(), "0101"); assert_eq!(a.count_ones(), 2); + +// Zero-copy views via BitStr +let view = a.as_bit_str(); // &BitString → BitStr +let sub = view.slice_from(1); // "010" +assert!(sub.starts_with(&BitString::try_from("01").unwrap().as_bit_str())); +assert_eq!(sub.count_ones(), 1); + +// Comparison +use core::cmp::Ordering; +assert_eq!(a.cmp(&b), Ordering::Less); // "1010" < "1100" + +// Convert back to owned +let owned = sub.to_bit_string(); +assert_eq!(owned.to_string(), "010"); ``` ## Benchmarks