crete 0.9.0

Ergonomic, thread-safe & flexible state management.
Documentation
# Crete

Crete is a procedural macro that simplifies state management in Rust, in a flexible way. 

It generates code for atomic access to a struct's fields using an `RwLock` and a static store, making it easier to work 
with shared state in both synchronous and asynchronous contexts.

Because users can implement practically anything on the struct, Crete allows for a flexible store that can be tailored 
to a variety of needs. 
Perhaps you'd like to use to build a Redux-style store.
Perhaps you enjoy having a billion custom setters.
Perhaps you just enjoy mutating everything directly.
Or maybe you even have your own homemade style of setters and getters that you love best?

No restrictions.

## Features

- **Ergonomic, intuitive Design**  
  Reduces boilerplate with a straightforward interface for managing state.

- **Synchronous and Asynchronous Support**  
  Works seamlessly in both synchronous and asynchronous environments.

- **Versatile Clone Support**  
  Adapts to your needs by supporting both cloneable and non-cloneable types.

- **No shoehorning**  
  Build the State management style you enjoy best on top... or not.

### Details

* Generates code for atomic, thread-safe access to a struct by defining a static store typically within a module 
that holds and manages the shared state.

* Generates unit structs (e.g. `FooField`, `BarField`) and implements a `Field` trait for each,
enabling type-safe access and update of individual fields.

 This macro produces:

 - A static store (a `LazyLock` holding an `RwLock` protecting an `Arc` of the struct)
   whose identifier is based on the struct name (e.g. `CRETE_FOO` for a struct named `Foo`).

 - An implementation of several associated methods on the struct:
     - `new()`: constructs a new instance using the struct's `Default` implementation. Called by `LazyLock`.
     - `read()`: returns an `Arc`-wrapped shared reference to the current state.
     - `clone()`: (if the struct implements `Clone`) returns a cloned instance of the stored value.
     - `write(self)`: atomically replaces the current stored value with the provided one.
     - `select_ref<F: Field>(&self, field: F) -> &F::FieldType`: returns a reference to the selected field.
     - `get<F, R>(field: F, f: impl FnOnce(&F::FieldType) -> R) -> R`: applies a closure to a shared reference
       of the selected field.
     - `select<F: Field>(field: F) -> F::FieldType`: returns a cloned value of the selected field.
     - `set<F>(field: F, value: F::FieldType)`: updates a specific field and writes the new state to the store.
     - `update(f: impl FnOnce(&mut Self))`: applies a mutation closure to the current state and updates the store.
     - `update_async(f: impl AsyncFnOnce(&mut Self))`: an asynchronous version of `update` for non-blocking mutations.

 The generated code leverages `std::sync::LazyLock`, `RwLock`, and `Arc` to ensure that all operations
 are safe to use concurrently from multiple threads.

## How it works


### Static Store

A static store is created for the struct, allowing atomic access to its state:

```rust
static #crete_store_ident: ::std::sync::LazyLock<::std::sync::RwLock<::std::sync::Arc<#struct_name>>> =
    ::std::sync::LazyLock::new(|| ::std::sync::RwLock::new(::std::sync::Arc::new(#struct_name::new())));
```

### With a struct like this:

```rust
use crete::Crete; 
 
#[derive(Crete, Default, Clone, Debug)] 
pub struct Store { 
 pub field1: String, 
 pub field_foo: String, 
 pub toggle: bool, 
 pub index: u32
}

impl Store {
    pub async fn inc1(&mut self) {
        self.index += 1;
    }

    pub fn dec2(&mut self) {
        self.index -= 2;
    }
}
```

### You can now do this:

```rust
#[test]
#[serial]
fn doc() {
    // Some initial values
    Store::set(Field1Field, "test value".to_string());
    Store::set(FieldFooField, "Foo".to_string());
    Store::set(ToggleField, true);
    Store::set(IndexField, 1000);

    /******************/
    /****** READ ******/
    /******************/
    
    // Get an owned value (only available if a field is Clone)
    let field1_value = Store::select(Field1Field);
    let field_foo_value = Store::select(FieldFooField);
    let toggle_value = Store::select(ToggleField);
    let index_value = Store::select(IndexField);
    assert_eq!(field1_value, "test value".to_string());
    assert_eq!(field_foo_value, "Foo".to_string());
    assert_eq!(toggle_value, true);
    assert_eq!(index_value, 1000);

    // Use the closure-based `get` method to get a reference
    Store::get(Field1Field, |value| {
        // `value` is a reference to the field
        assert_eq!(value, &"test value".to_string());
    });

    // You could also get a reference via binding since this is an RWLock<Arc<F>
    {
        let binding = Store::read();
        let field1_ref = binding.select_ref(Field1Field);
        assert_eq!(field1_ref, &"test value".to_string());
    }

    /******************/
    /****** WRITE *****/
    /******************/

    // Update via closure
    Store::update(|s| { // &mut Store
        s.field1 = "updated value".to_string();
        s.dec2();
    });
    assert_eq!(Store::select(Field1Field), "updated value".to_string());
    assert_eq!(Store::read().index, 998);

    // And we can use `set()` as we saw earlier
    Store::set(FieldFooField, "Foo".to_string());
    assert_eq!(Store::select(FieldFooField), "Foo".to_string());
}
```

### Async closure support

```rust
    #[tokio::test]
    async fn doc2() {
        Store::set(IndexField, 1000);

        // Async closure
        Store::update_async(async |s| { // &mut Store
            s.field1 = "updated value 2".to_string();
            s.inc1().await;
            s.inc1().await;
            s.inc1().await;
        }).await;

        assert_eq!(Store::select(IndexField), 1003);
        Store::get(Field1Field, |value| {
            assert_eq!(value, "updated value 2");
        });
    }
```

### No Clone, No Problem

It works the same way, except:
* `select()` does not exist for fields that are not clone-able.
* `clone()` does not exist for the static Struct.

```rust
#[cfg(test)]
mod tests_no_clone {
    use tokio;
    use crete::Crete;

    #[derive(Debug, PartialEq)]
    pub struct NotCloneType {
        pub value: i32,
    }

    impl Default for NotCloneType {
        fn default() -> Self {
            NotCloneType { value: 0 }
        }
    }

    #[derive(Crete, Default)]
    pub struct Store {
        pub foo: NotCloneType,
    }

    impl Store {
        async fn reset(&mut self) {
            self.foo = NotCloneType::default();
        }

        fn init(&mut self) {
            self.foo = NotCloneType { value: 42 };
        }
    }

    #[tokio::test]
    async fn foobar() {
        Store::set(FooField, NotCloneType { value: 100 });
        Store::get(FooField, |v| {
            assert_eq!(v, &NotCloneType { value: 100 });
        });

        Store::update_async(async |s| {
            s.init();
            assert_eq!(s.foo, NotCloneType { value: 42 });

            s.reset().await;
        }).await;
        Store::get(FooField, |v| {
            assert_eq!(v, &NotCloneType { value: 0 });
        });
    }
}
```

### Considerations

You are using `RWLock` behind the scenes. The usual considerations for locking in multithreaded code apply.

In essence:
* Don't let your thread panic while it holds a write [lock]https://doc.rust-lang.org/src/std/sync/poison/rwlock.rs.html#370-375.
* If the thread already has acquired a lock, [don't]https://doc.rust-lang.org/src/std/sync/poison/rwlock.rs.html#460-465 try to get it again before it drops.

## FAQ

### What's with the name?

* It's interestingly confusing with `crate`.
* It's the name of the island the crate author spends lots of his time at.

## License

This project is licensed under the MIT License._