Support generic value types (SparKV<V>)#1
Conversation
Make the value type generic, defaulting to String so existing usage keeps working. Add a ValueSize trait (with built-in impls for strings, byte buffers, and scalars) for max_item_size enforcement, and make max_item_size an Option so the limit can be disabled. Update README and tests. Breaking change: bump to 0.2.0.
uzyn
left a comment
There was a problem hiding this comment.
Review: Support generic value types (SparKV)
Reviewed at commit 6e67752. I built, tested, linted, and independently compiled the README examples. Verdict up front: Ready to merge — no blockers. A handful of non-blocking polish items are listed below for tracking.
Changes Overview
Makes the store generic over its value type: SparKV<V = String> and KvEntry<V = String>, with String as the default so the bare type name and existing string usage keep working. Introduces a ValueSize trait (impls for String/&str/str, Vec<u8>/&[u8]/[u8], and all scalars) so max_item_size enforcement works for any value type, and changes Config.max_item_size from usize to Option<usize> (None disables per-entry size enforcement). Trait bounds are placed on impls rather than the struct: set/set_with_ttl need V: ValueSize, get needs V: Clone, delete needs neither (moves the value out). README gets a generic-types section and a 0.1→0.2 migration guide; version bumped to 0.2.0.
Verification
cargo test— 24 passed, 0 failed.cargo clippy --all-targets -- -D warnings— clean.cargo fmt --check— clean.- Compiled the README quick-start,
i64,Session, and aVec<u8>example as a real binary — all compile and run.
Scope Alignment
The implementation matches the stated intent precisely, and the README TODO item it claims to close was removed. One thing the "Breaking changes (0.2.0)" list omits:
- Undocumented breaking change:
KvEntrylost itsPartialEq/Eqderives (src/kventry.rs:1). It went from#[derive(Debug, Clone, PartialEq, Eq)]to#[derive(Debug, Clone)].KvEntryis a public re-export (pub use kventry::KvEntry), so any downstream code comparingKvEntryvalues with==will stop compiling. Dropping the derive is a reasonable call (deriving it generically would forceV: PartialEq), but it should be listed in the breaking-changes section. Non-blocker.
Potential Bugs
No correctness bugs in the changed code. I specifically checked that size-enforcement semantics are preserved for the default String store: the old path compared value.len(), and ValueSize for String returns self.len(), so behavior is identical. The Option<usize> gate (src/lib.rs:139) correctly skips enforcement only when None.
One pre-existing issue, called out as context only (not introduced or touched by this PR, so not a blocker here): clear_expired does self.data.get(&exp_item.key).unwrap() (src/lib.rs:69). If a key is deleted manually, its stale ExpEntry remains in the heap (delete intentionally doesn't remove from the heap), and if that entry later reaches the top while expired, the unwrap() panics because the key is gone from data. Worth a follow-up issue, but it predates this PR and is out of scope for this review.
Security Issues
None. In-memory library, no input parsing, no unsafe, no new dependencies.
Test Coverage
The new tests are meaningful: generic i64 store round-trip + delete, size enforcement on a generic type, unbounded behavior when max_item_size = None, and ValueSize unit tests. Gaps (all non-blocking):
- The "
deleteneeds noClone" design claim is untested. This is a headline feature of the bound design, but every value type exercised in tests isClone. A test storing a move-only type (a struct that implsValueSizebut notClone), thendelete-ing it, would actually provedeleteworks withoutCloneand guard the bound from regressing. I confirmed manually that it compiles, but nothing in the suite locks it in. - README examples aren't doctests, so
cargo testreportsDoc-tests sparkv ... 0 tests. TheSessionandVec<u8>examples compile today (I checked), but they're unguarded against future drift. Converting the key snippets to doctests would keep them honest. - No store-level test for a byte-buffer value (
Vec<u8>) — only theValueSizeunit test covers byte sizing. Minor.
Code Quality
impl ValueSize for strandimpl ValueSize for [u8](src/value_size.rs:12,:30) are effectively unreachable through the store:Vmust beSizedto live inKvEntry<V>, soSparKV<str>/SparKV<[u8]>can never exist. They compile and could serve generic callers of the trait directly, but within this crate they're dead. Harmless — flagging only so it's a conscious choice.setrequiresV: ValueSizeeven whenmax_item_size = None. Disabling enforcement doesn't lift the trait requirement, so a type with no meaningful size still needs a stubvalue_sizeimpl. The README documents "return 0 if you don't care," so this is acceptable, just an API-ergonomics note.charreportssize_of::<char>()= 4 regardless of its UTF-8 byte length. Consistent with the "logical size" framing but mildly surprising next toString/strusing byte length. Trivial.
Summary and Recommended Actions
Overall verdict: Ready to merge. Clean implementation, faithful to the stated scope, all checks green, examples verified.
Blockers: none.
Non-blockers (worth addressing, not blocking):
- Document the
KvEntryPartialEq/Eqderive removal in the breaking-changes list. - Add a test that stores and
deletes a non-Clonevalue type to lock in the "delete needs no Clone" design.
Nice-to-haves (track for later):
- Convert key README snippets to doctests so they're compiled by
cargo test. - Add a store-level test for a
Vec<u8>value type. - Decide whether the unsized
str/[u8]ValueSizeimpls are worth keeping. - (Pre-existing, separate issue)
clear_expiredcan panic viaunwrap()after a manualdeleteleaves a stale heap entry.
Recommended merge commit message:
Support generic value types (SparKV<V>)
Make the value type generic, defaulting to String so existing usage keeps
working. Add a ValueSize trait (built-in impls for strings, byte buffers, and
scalars) so max_item_size enforcement works for any value type, and make
max_item_size an Option so the limit can be disabled. Trait bounds live on
impls: set requires V: ValueSize, get requires V: Clone, delete needs neither.
Update README with a generic-types section and a 0.1->0.2 migration guide.
Breaking change: bump to 0.2.0.
Summary
Makes SparKV generic over its value type instead of being hardcoded to
String, addressing the "Support generic data types" item in the README TODO.SparKV<V = String>andKvEntry<V = String>— value type is now generic, defaulting toStringso the bare type name and string-centric usage still work. Keys remainString.ValueSizetrait (fn value_size(&self) -> usize) with built-in impls forString,&str,Vec<u8>,&[u8], and all scalar types.max_item_sizeenforcement now works for any value type; custom types add a one-line impl.Config.max_item_sizeis nowOption<usize>(defaultSome(500_000)); set it toNoneto disable per-entry size enforcement.set/set_with_ttlrequireV: ValueSize,getrequiresV: Clone,deletemoves the value out (noCloneneeded).Migrating from 0.1 to 0.2
This is a breaking release (
0.2.0), but the migration is small and mechanical. The store behaves exactly as before; only value passing andmax_item_sizeconfig change. TheSparKVandKvEntrytype names still resolve to theirStringforms, so type annotations and struct fields are unaffected. A migration guide is also included in the README.1.
set/set_with_ttltake the value by value — pass an owned value instead of&str:getanddeletestill returnOption<String>for the defaultStringstore, so read sites do not change.2.
Config.max_item_sizeis nowOption<usize>— wrap the limit inSome, or useNoneto disable enforcement:3. Custom value types need a
ValueSizeimpl — only relevant if you switch away fromString. Built-in types (integers,Vec<u8>,&[u8], etc.) already implement it.No other call sites change.
Breaking changes (0.2.0)
set(key, value: V)takes the value by value.get/deletereturnOption<V>(stillOption<String>for the default).getrequiresV: Clone;setrequiresV: ValueSize.Config.max_item_sizechanged fromusizetoOption<usize>.Testing
cargo test— 24 passed (19 existing adapted + 5 new: generic int store, generic size enforcement, unbounded-when-None, and twoValueSizeunit tests).cargo clippy --all-targets -- -D warnings— clean (also cleaned up a few pre-existing test-style lints).cargo fmt --check— clean.