A zero-cost abstraction over heap allocation and reference counting for ?Sized
types, built for performance-critical systems with manual memory control.
While developing kodrst
, we encountered non-trivial overhead from using Arc<str>
in performance-sensitive paths. Even in immutable scenarios, Arc
introduces unnecessary synchronization and layout complexity. kroos
provides low-level primitives (Flake
, Rime
) designed to bypass these costs entirely, trading away safety and general-purpose semantics in favor of raw performance.
- You want to store unsized data like
str
or[u8]
on the heap with no overhead. - You want shared ownership over unsized types, but don't want to pay for
Arc
orRc
. - You need maximum control over memory layout, reference counting, or allocation.
- You can guarantee safe memory usage manually (e.g., no
Drop
, aliasing, or race conditions).
- Your types require
Drop
, or have complex ownership semantics. - You want ergonomic or type-safe abstractions.
- You're not prepared to work at the raw pointer level.
A Box
-like wrapper for unsized types without ownership semantics. It provides heap allocation for types like str
, [u8]
, or any ?Sized
data using raw pointers.
Allocates a copy of a referenced ?Sized
object to the heap.
let flake = Flake::new("hello");
assert_eq!(&*flake, "hello");
Moves a Sized
value directly into the heap, without duplication.
let flake = Flake::steal(String::from("hi"));
assert_eq!(&*flake, "hi");
A Flake<T>
contains a single fat pointer to the heap-allocated value. It does not store length or capacity explicitly — metadata is embedded in the fat pointer.
While Flake
is primarily intended for immutable data, controlled mutation is allowed:
let mut flake = Flake::new(&[1, 2, 3][..]);
unsafe { (*flake.as_mut_ptr())[0] = 42; }
assert_eq!(&*flake, &[42, 2, 3]);
Caution
Use only if you guarantee exclusive access.
A compact, inline, reference-counted pointer to unsized types, using a custom Counter
.
- Fully inline:
Rime
stores[ Counter | Data ]
in one allocation. - Works with dynamically sized types (
str
,[u8]
, etc.) - Counter-agnostic: atomic or non-atomic counters via
Rime<AtomicU8, str>
orRime<Cell<u8>, str>
. - No vtable, no indirection.
Copies a reference to heap and initializes a new refcount.
let rime = Rime::<AtomicU8, str>::new("hello");
let clone = rime.clone();
assert_eq!(&*clone, "hello");
Takes ownership of a Sized
value and moves it into a single block.
let rime = Rime::<u8, String>::steal("hi".to_string());
assert_eq!(&*rime, "hi".to_string());
If you use a Cell<u8>
counter (or similar interior-mutable strategy), you can achieve safe mutability under the following constraints:
Rime
must not be cloned (i.e. you must be the only owner).- Use
as_mut_ptr
to mutate contents in-place. - You are responsible for enforcing Rust’s aliasing rules manually.
You can store metadata in a header format like:
[ Counter | Metadata | Data ]
↑ ↑
len/cap T
For example, a fixed-capacity string-like object might store len
as a usize
, followed by a zero-terminated buffer. On mutation:
- Update
len
manually. - Overwrite data in-place within bounds.
This enables a fixed-capacity "string" stored inline, avoiding reallocation.
Flake::from_raw
and Rime::from_raw
allow you to construct heap-backed references without the intermediate cost of stack allocation followed by a move. Instead of creating a Box<T>
or Vec<T>
and extracting its pointer, you can allocate memory and write directly into it.
For example, to create a *mut str
without touching the stack:
pub unsafe fn alloc_str_and_write(bytes: &[u8]) -> *mut str {
let len = bytes.len();
let ptr = alloc(Layout::array::<u8>(len).unwrap());
// You can handle layouts errors checking ptr.in_null()
ptr.copy_from_nonoverlapping(bytes.as_ptr(), len);
ptr::slice_from_raw_parts_mut(ptr, len) as *mut str
}
You can then wrap the result into a Flake
:
let raw = unsafe { alloc_str_and_write(b"hola") };
let flake = unsafe { Flake::from_raw(raw) };
This pattern avoids temporary allocations or extra indirection like:
let boxed = Box::new(value); // Allocates and moves
let flake = Flake::new(&*boxed); // Copies again
Instead, you allocate once and write directly where the value will live.
Note
Box is optimized for sometimes do a placement-in protocol
-like.
Feature | Box / Arc |
Flake / Rime |
---|---|---|
Works with ?Sized |
✅ (Box ) |
✅ |
Custom counter | ❌ | ✅ |
Atomic optional | ✅ (Arc ) |
✅ (via AtomicU* ) |
Inline allocation | ❌ | ✅ |
Copy or move control | ❌ | ✅ |
Drop safety | ✅ | ❌ (must be manual) |
Flake
andRime
bypassDrop
,Clone
, and Rust's ownership model.- Do not use with types that manage heap resources or contain non-
Copy
fields. - You are responsible for:
- Ensuring uniqueness or correct refcounting.
- Avoiding data races (use
Atomic*
if needed). - Deallocating correctly (done automatically, but only if created via safe constructors).
This project is licensed under the GNU AGPL v3
.
All contributions must retain clear attribution to the original author(s).
If you modify or extend this project, please include appropriate credit in your repository and documentation.
In particular:
- Keep the original copyright notices.
- If publishing a fork, mention the original project name and link in your README.
- If you use this project as a dependency, consider acknowledging it in your LICENSE or README.
I believe in free software and shared credit.
This crate exists to explore precision memory control and support edge-case optimizations in projects like kodrst
. It is not intended as a general-purpose utility. If you're using Flake
or Rime
outside internal systems, you're either very brave — or very desperate...