tendrils

Crates.iotendrils
lib.rstendrils
version0.1.0
created_at2025-10-09 18:25:34.093783+00
updated_at2025-10-09 18:25:34.093783+00
descriptionTiny, flexible helpers for managing shared state (Arc> / Arc>) safely in async Rust.
homepage
repositoryhttps://github.com/crookedlungs/tendrils
max_upload_size
id1876046
size32,918
Ryon Boswell (crookedlungs)

documentation

README

๐ŸŒฟ tendrils

Crates.io Docs.rs 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.

๐Ÿชถ "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:

[dependencies]
tendrils = "0.2"

Optionally enable features:

[dependencies]
tendrils = { version = "0.2", features = ["tokio", "saydbg"] }

Available Features

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 โŒ

๐ŸŒฑ Quick Start

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;
}

โš™๏ธ Helper Functions

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:

  • 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.

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>>

๐Ÿ” Using with 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.


๐Ÿงฉ Example: Shared App State

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.


๐Ÿ’ก Author

Ryon Boswell โ€” @crookedlungs
A developer and educator creating lightweight, modular tools for Rust and education.


๐Ÿฆ€ "Mutate safely. Grow naturally." โ€” tendrils

Commit count: 0

cargo fmt