| Crates.io | crete |
| lib.rs | crete |
| version | 0.10.0 |
| created_at | 2025-03-12 18:28:39.656911+00 |
| updated_at | 2025-03-15 16:22:54.520011+00 |
| description | Ergonomic, thread-safe & flexible state management. |
| homepage | |
| repository | https://github.com/kapolos/crete |
| max_upload_size | |
| id | 1590137 |
| size | 21,778 |
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.
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.
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. Available only for T that derives Clone.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. Available only for T that derives Clone.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.
A static store is created for the user's struct, allowing atomic access to its state:
When T is Clone:
LazyLock<RwLock<Arc<T>>>
Otherwise:
LazyLock<Arc<RwLock<T>>>
use crete::crete;
#[crete()]
#[derive(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;
}
}
#[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());
}
#[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");
});
}
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.#[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 }
}
}
#[crete()]
#[derive(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 });
});
}
}
The proc macro tries to infer if the struct derives Clone. This works if you place it before the derive macro.
// Works
#[crete()]
#[derive(Clone)]
You can also be explicit about it (also useful if you impl Clone instead of deriving it):
// Works
#[crete(Clone)]
#[derive(Clone)]
This won't work:
// Fails
#[derive(Clone)]
#[crete()]
But this will work:
// Works
#[derive(Clone)]
#[crete(Clone)]
There is a different implementation depending on your struct.
In this case, we use RwLock<Arc<T>>. This means readers are not blocked by writers.
In this case, we use Arc<RwLock<T>. This means both readers and writers block.
You are using RwLock behind the scenes. The usual considerations for locking in multithreaded code apply.
In essence:
crate.This project is licensed under the MIT License._