| Crates.io | tendrils |
| lib.rs | tendrils |
| version | 0.1.0 |
| created_at | 2025-10-09 18:25:34.093783+00 |
| updated_at | 2025-10-09 18:25:34.093783+00 |
| description | Tiny, flexible helpers for managing shared state (Arc |
| homepage | |
| repository | https://github.com/crookedlungs/tendrils |
| max_upload_size | |
| id | 1876046 |
| size | 32,918 |
Tendrils is a minimal, ergonomic helper crate for managing shared mutable state in Rust โ built on top of Arc<Mutex<T>> or Arc<RwLock<T>>.
It provides convenient async-ready wrappers, scoped lock helpers, and optional debug metrics via saydbg.
๐ชถ "Lightweight state management for async Rust apps."
โ
Simple and type-safe wrappers around Arc<Mutex<T>> and Arc<RwLock<T>>
โ
Async-ready (tokio or parking_lot)
โ
Scoped access โ locks are automatically dropped before .await
โ
Optional debug metrics integration with [saydbg]
โ
Zero-cost abstraction: you can still access the inner Arc if needed
Add to your Cargo.toml:
[dependencies]
tendrils = "0.2"
Optionally enable features:
[dependencies]
tendrils = { version = "0.2", features = ["tokio", "saydbg"] }
| Feature | Description | Default |
|---|---|---|
tokio |
Use tokio::sync::{Mutex, RwLock} for async contexts |
โ |
parking_lot |
Use parking_lot::{Mutex, RwLock} for sync contexts |
โ |
saydbg |
Enables colorful debug metrics during lock operations | โ |
use tendrils::Tendrils;
#[derive(Default)]
struct AppState {
counter: u32,
}
#[tokio::main]
async fn main() {
// Create shared state
let state = Tendrils::new(AppState::default());
// Scoped mutation
state.with(|s| s.counter += 1).await;
// Async-safe mutation (drops lock before await)
state.with_async(|s| async move {
s.counter += 10;
}).await;
// Non-blocking attempt
if let Some(new_value) = state.try_with(|s| { s.counter += 1; s.counter }) {
println!("Updated count: {new_value}");
}
}
RwTendrils<T> (ReadโWrite variant)Use this when your state is read frequently and written occasionally.
use tendrils::RwTendrils;
#[derive(Default)]
struct Config {
theme: String,
}
#[tokio::main]
async fn main() {
let config = RwTendrils::new(Config { theme: "light".into() });
// Write
config.write(|c| c.theme = "dark".into()).await;
// Read
config.read(|c| assert_eq!(c.theme, "dark")).await;
}
Tendrils includes helper functions for direct use with your own state types:
// Scoped synchronous mutation
with_state(&state, |s| s.do_something()).await;
// Scoped async mutation
with_state_async(&state, |s| async move { s.do_other_thing().await }).await;
// Non-blocking access
try_with_state(&state, |s| s.maybe_do_work());
// For RwLocks
with_state_read(&rw_state, |s| s.read_data()).await;
with_state_write(&rw_state, |s| s.modify_data()).await;
All helpers:
.await pointstokio or parking_lot)Tendrils<T>A minimal wrapper over Arc<Mutex<T>> with ergonomic access methods.
| Method | Description |
|---|---|
new(inner: T) |
Create a new Tendrils from a value |
from_arc(Arc<Mutex<T>>) |
Wrap an existing Arc |
arc() |
Clone the inner Arc<Mutex<T>> |
with(f) |
Lock and mutate synchronously |
with_async(f) |
Async mutation that drops the lock before .await |
try_with(f) |
Non-blocking attempt to mutate |
RwTendrils<T>A readโwrite wrapper over Arc<RwLock<T>>.
| Method | Description |
|---|---|
new(inner: T) |
Create a new RwTendrils |
read(f) |
Scoped read access |
write(f) |
Scoped write access |
arc() |
Clone the inner Arc<RwLock<T>> |
saydbg (optional)Enable the saydbg feature to automatically print debug info about lock usage:
[dependencies]
tendrils = { version = "0.1", features = ["tokio", "saydbg"] }
Example output:
[debug] Acquiring lock for Tendrils<AppState>
[debug] Released lock for Tendrils<AppState>
Perfect for tracing async lock contention and measuring task flow.
use tendrils::{Tendrils, with_state_async};
use std::time::Duration;
use tokio::time::sleep;
#[derive(Default)]
struct SharedState { value: u32 }
#[tokio::main]
async fn main() {
let state = Tendrils::new(SharedState::default());
// Task A
let a = state.clone();
tokio::spawn(async move {
a.with_async(|s| async move {
s.value += 1;
sleep(Duration::from_millis(50)).await;
}).await;
});
// Task B
state.with(|s| s.value += 10).await;
println!("Final value: {}", state.with(|s| s.value).await);
}
Licensed under the MIT License.
Ryon Boswell โ @crookedlungs
A developer and educator creating lightweight, modular tools for Rust and education.
๐ฆ "Mutate safely. Grow naturally." โ
tendrils