tendrils 0.1.0

Tiny, flexible helpers for managing shared state (Arc<Mutex<T>> / Arc<RwLock<T>>) safely in async Rust.
Documentation
# ๐ŸŒฟ tendrils

[![Crates.io](https://img.shields.io/crates/v/tendrils.svg)](https://crates.io/crates/tendrils)
[![Docs.rs](https://docs.rs/tendrils/badge.svg)](https://docs.rs/tendrils)
[![License](https://img.shields.io/crates/l/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

| 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

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

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