# ๐ฟ tendrils
[](https://crates.io/crates/tendrils)
[](https://docs.rs/tendrils)
[](./LICENSE)
**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`](https://crates.io/crates/saydbg).
> ๐ชถ _"Lightweight state management for async Rust apps."_
---
## โจ Features
โ
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
---
## ๐ฆ Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
tendrils = "0.2"
```
Optionally enable features:
```toml
[dependencies]
tendrils = { version = "0.2", features = ["tokio", "saydbg"] }
```
### Available Features
| `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 | โ |
---
## ๐ฑ Quick Start
```rust
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.
```rust
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;
}
```
---
## โ๏ธ Helper Functions
Tendrils includes helper functions for direct use with your own state types:
```rust
// Scoped synchronous mutation
with_state(&state, |s| s.do_something()).await;
// Scoped async mutation
// 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:
- Acquire and drop locks automatically
- Avoid holding locks across `.await` points
- Are backend-agnostic (`tokio` or `parking_lot`)
---
## ๐ง Wrapper Types
### `Tendrils<T>`
A minimal wrapper over `Arc<Mutex<T>>` with ergonomic access methods.
| `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>>`.
| `new(inner: T)` | Create a new `RwTendrils` |
| `read(f)` | Scoped read access |
| `write(f)` | Scoped write access |
| `arc()` | Clone the inner `Arc<RwLock<T>>` |
---
## ๐ Using with `saydbg` (optional)
Enable the `saydbg` feature to automatically print debug info about lock usage:
```toml
[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.
---
## ๐งฉ Example: Shared App State
```rust
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);
}
```
---
## ๐ชถ License
Licensed under the [MIT License](./LICENSE).
---
## ๐ก Author
**Ryon Boswell** โ [@crookedlungs](https://github.com/crookedlungs)
A developer and educator creating lightweight, modular tools for Rust and education.
---
> ๐ฆ _"Mutate safely. Grow naturally."_ โ `tendrils`