Utility crate of composable building blocks for time handling, strings, binary helpers, collection ergonomics, and small async/Tokio primitives.
[dependencies]
rust-extensions = { tag = "${last_tag}", git = "https://github.com/MyJetTools/rust-extensions.git" }with-tokio— Tokio-backed helpers: timers, queues, events loop, task completions, application state tracking, sortable IDs.base64— Enable base64 encode/decode utilities.hex— Enable hex helpers.objects-pool— Object pooling.vec-maybe-stack— Stack-or-heap small-buffer optimization helpers.
Example:
rust-extensions = { version = "${last_tag}", features = ["with-tokio", "base64"] }- Time:
date_time,duration_utils,stop_watch,atomic_stop_watch,atomic_duration. - String ergonomics:
short_string,maybe_short_string,string_builder,str_utils,str_or_string,as_str. - Binary helpers:
binary_payload_builder,binary_search,uint32_variable_size, optionalbase64, optionalhex. - Collections & memory:
sorted_vec,sorted_ver_with_2_keys,grouped_data,auto_shrink,slice_or_vec,vec_maybe_stack(opt),objects_pool(opt),lazy,linq,array_of_bytes_iterator,slice_of_u8_utils. - Async/Tokio (feature
with-tokio):events_loop,my_timer,task_completion,tokio_queue,queue_to_save,queue_to_save_with_id,application_states,sortable_id. - IO & misc:
file_utils,remote_endpoint,logger,min_value,max_value,min_key_value,placeholders,maybe_short_string.
- Time point + interval keys:
date_time::DateTimeAsMicrosecondsfor UTC timestamps with µs precision.date_time::interval_key::*for rounding/grouping into year/month/day/hour/minute/minute5 buckets.
- High-performance strings:
ShortString(Pascal-style, single-byte length, max 255 bytes on stack) withDisplay,Serialize,Eq, hashing.
MaybeShortStringkeeps data inline asShortStringwhen length ≤ 255 bytes; seamlessly upgrades toStringwhen longer.StringBuilderfor incremental push/format operations.
- Binary payloads:
BinaryPayloadBuilderto append scalars, slices, and length-prefixed data into a singleVec<u8>.Uint32VariableSizefor compact integer encoding/decoding.
- Collections:
SortedVecfamily:SortedVec,SortedVecWithStrKey,SortedVecOfArc,SortedVecOfArcWithStrKey, andSortedVecWith2Keysmaintain order on insert and support efficient lookups.AutoShrinkVec/AutoShrinkVecDequeresize toward steady-state usage.SliceOrVectoggles between borrowed and owned buffers.
- Async/Tokio (enable
with-tokio):MyTimerfor tick-driven tasks,EventsLoopfor fan-out processing,TaskCompletionfor awaiting completion handles,TokioQueuefor bounded async queues,QueueToSavefor producer/consumer disk pipelines,ApplicationStatesfor async state transitions.
- File/IO:
file_utils::read_file_lines_iter,array_of_bytes_iterator::FileIterator,remote_endpointhelpers for host/port parsing.
DateTimeAsMicroseconds is a single-field UTC timestamp (unix_microseconds: i64) with serde support and helpers to add/subtract durations, compare, format (RFC 3339/2822/5322/7231, compact), and convert to chrono::DateTime<Utc>.
Interval keys let you cut timestamps to buckets:
- Compile-time typed:
IntervalKey<YearKey | MonthKey | DayKey | HourKey | MinuteKey | Minute5Key>. - Runtime enum:
DateTimeInterval::{Year, Month, Day, Hour, Minute, Min5}. - Conversions are zero-cost wrappers over
i64values; you can go fromDateTimeAsMicrosecondsto an interval key and back.
Minimal example:
use rust_extensions::date_time::*;
use rust_extensions::date_time::interval_key::*;
use std::time::Duration;
let now = DateTimeAsMicroseconds::now();
let minute_key: IntervalKey<MinuteKey> = now.into();
let next_minute = minute_key.add(Duration::from_secs(60));
assert!(next_minute.to_i64() >= minute_key.to_i64());ShortString: Pascal-style layout (length stored in the first byte) backed by[u8; 256], so the maximum encoded length is 255 bytes. Supports UTF-8 chars, checkedtry_push/try_push_strand panickingpush/push_str, serde, comparison, and case-insensitive helpers. Ideal for small IDs, headers, and keys without heap allocations.MaybeShortString: stores asShortStringwhile length ≤ 255 bytes; automatically promotes toStringonce it would overflow, so you can push without manual branching.StrOrString/SliceOrVecfor zero-copy borrow-or-own patterns.StringBuilder: push bytes/strings/char, drain toStringwithout realloc churn.
Example:
use rust_extensions::ShortString;
let mut s = ShortString::from_str("hi").unwrap();
assert!(s.try_push('-'));
s.push_str("there");
assert_eq!(s.as_str(), "hi-there");BinaryPayloadBuilder: append primitives (u8,u16,u32,u64, slices) and length-prefixed blobs; get the finalVec<u8>or borrowed slice.Uint32VariableSize: encode variable-lengthu32values for compact wire/storage formats.- Optional:
base64andhexmodules expose encode/decode helpers compatible with the rest of the crate.
Example:
use rust_extensions::binary_payload_builder::BinaryPayloadBuilder;
let mut builder = BinaryPayloadBuilder::new();
builder.write_u16(42);
builder.write_slice(b"ping");
let bytes = builder.finish();
assert_eq!(bytes.len(), 2 + 4);SortedVec<T>/SortedVecWith2Keys<K1, K2, V>keep elements ordered; provide binary search insertion and lookup APIs.GroupedDatato collect items by key with minimal allocations.AutoShrinkVec/AutoShrinkVecDequeshrink capacity after spikes.ObjectsPool(featureobjects-pool) for pooling reusable buffers/objects.VecMaybeStack(featurevec-maybe-stack) for small-buffer-optimized vectors.- Iteration helpers:
array_of_bytes_iterator::{SliceIterator, VecIterator, FileIterator},slice_of_u8_utilsfor safe chunking. Lazy<T>for deferred construction guarded byOnceLock-like behavior.
EventsLoop: single-consumer async message loop —sendis lock-free, the consumer runs in a dedicated Tokio task.MyTimer: tick-based scheduling with graceful stop.TaskCompletion: create awaitable completion sources with error support.TokioQueue: bounded async queue with backpressure.QueueToSave: producer/consumer file-saving pipeline with retries.QueueToSaveWithId: same producer/consumer batching asQueueToSave, but each item implementsPersistObjectId<ID>. Re-enqueuing an item with an ID already in the queue overwrites the pending entry, so only the latest state per ID is flushed to the handler.IDmust beHash + Eq + Clone; the handler receives aVec<T>per tick. No ordering guarantee across IDs.ApplicationStates: async state machine with callbacks.SortableId: monotonic sortable IDs backed by time + randomness.
#[cfg(feature = "with-tokio")]
async fn example_queue() {
use rust_extensions::tokio_queue::TokioQueue;
let queue = TokioQueue::new(100);
queue.send("item").await.unwrap();
let item = queue.recv().await.unwrap();
assert_eq!(item, "item");
}EventsLoop is designed to live inside an AppCtx as a plain field (no outer Mutex / no mut access needed). The flow:
- Construct in
AppCtx::new— the channel is created immediately, sosendis available right away and is lock-free (no mutex on the hot path). - Register a callback (
EventsLoopTick) viaregister_event_loop— typically during app initialization, once dependencies are wired. - Start — spawns the background reader task which owns the receiver + callback and drives
started/tick/finished. - Send / stop —
send(msg)pushes a message;stop()sends a shutdown signal.
#[cfg(feature = "with-tokio")]
mod example {
use std::sync::Arc;
use rust_extensions::{
events_loop::{EventsLoop, EventsLoopTick},
ApplicationStates, Logger,
};
// 1. Define what each tick should do.
struct MyHandler;
#[async_trait::async_trait]
impl EventsLoopTick<String> for MyHandler {
async fn started(&self) {
println!("loop started");
}
async fn tick(&self, model: String) {
println!("got message: {model}");
}
async fn finished(&self) {
println!("loop finished");
}
}
// 2. Keep it inside AppCtx without any outer Mutex.
pub struct AppCtx {
pub events_loop: EventsLoop<String>,
}
impl AppCtx {
pub fn new() -> Self {
Self {
events_loop: EventsLoop::new("my-loop"),
}
}
}
// 3. Wire up at startup.
pub async fn bootstrap(
ctx: Arc<AppCtx>,
app_states: Arc<dyn ApplicationStates + Send + Sync + 'static>,
logger: Arc<dyn Logger + Send + Sync + 'static>,
) {
ctx.events_loop
.register_event_loop(Arc::new(MyHandler))
.await;
ctx.events_loop.start(app_states, logger).await;
}
// 4. Produce messages from anywhere — `send` takes `&self` and never locks.
pub fn produce(ctx: &AppCtx) {
ctx.events_loop.send("hello".to_string());
}
}For producers that don't need access to the whole EventsLoop (e.g. a background task, an HTTP handler held in its own struct), grab a cheap reference-counted publisher:
use std::sync::Arc;
use rust_extensions::events_loop::EventsLoopPublisher;
let publisher: Arc<EventsLoopPublisher<String>> = ctx.events_loop.get_publisher();
// Move/clone the Arc into other tasks; `send` / `stop` work the same way and stay lock-free.
tokio::spawn({
let publisher = publisher.clone();
async move {
publisher.send("from background task".into());
}
});get_publisher returns Arc<EventsLoopPublisher<TModel>> — every call hands out a clone of the same shared publisher (the Sender is created once in EventsLoop::new).
Key properties:
- Lock-free
send/stop— theSenderlives inside the sharedEventsLoopPublisher;.lock()is only ever taken inregister_event_loopandstart. - One-shot registration — a second
register_event_looppanics;startwithout a prior register panics. - Bounded lifecycle —
stopdeliversShutdownthrough the same channel, so in-flight messages ahead of it are processed first. - Per-tick timeout —
set_iteration_timeout(Duration)caps a singletickcall; overruns are logged via the providedLoggerand the loop keeps running.
file_utils: iterators over file lines and path helpers.logger: simple structured logger traits.remote_endpoint: parse/format endpoints (host:port), detect loopback.- Math/min-max helpers:
min_value,max_value,min_key_value,max_value. StopWatch/AtomicStopWatch/AtomicDurationfor timing.
- License: MIT (see
LICENSE.md). - Current crate version: 0.1.5.
- Contributions are welcome via pull requests.