Quick Start
Defining state
Section titled “Defining state”Use the #[amethystate] macro to declare a state struct. The prefix sets the namespace under which fields are stored.
use amethystate::amethystate;
#[amethystate(prefix = "network")]pub struct NetworkState { #[amestate(default = "127.0.0.1".to_string())] pub host: String,
#[amestate(default = 8080)] pub port: u16,}Initializing the store
Section titled “Initializing the store”Global store
Section titled “Global store”The simplest approach. Initialize once at startup, then access state anywhere without passing the store around.
use amethystate::{IntoGlobalStore, StoreBuilder};
// From a path string"./app.redb".init_global();
// Platform config directory (XDG on Linux, AppData on Windows, Application Support on macOS)StoreBuilder::for_app("my-app", "settings")?.init_global();
// With optionsStoreBuilder::for_app("my-app", "settings")? .debounce(500) .init_global();
let state = NetworkState::new().unwrap();Explicit store
Section titled “Explicit store”If you prefer to manage the store lifetime yourself:
use amethystate::StoreBuilder;
fn main() -> amethystate::Result<()> { let store = StoreBuilder::new("./app.redb") .debounce(500) .build()?;
let state = NetworkState::new_with(&store)?; Ok(())}Reading and writing
Section titled “Reading and writing”// Readprintln!("{}", state.host().get());
// Write — persists to buffer immediately, flushes to disk debouncedstate.port().set(9090)?;
// Subscribe to changeslet _sub = state.port().subscribe(|p| { println!("port changed to {p}");});Persistent-only mode
Section titled “Persistent-only mode”For frameworks that own their update loop (egui, iced, ratatui), use mode = "persistent". Fields become plain Rust types with no reactive overhead — a confy-like API.
Note that persistent-only state does not observe external changes. If another part of the application writes to the same store, or the underlying file is modified externally, the loaded struct will not update. Use reactive mode if you need that.
#[amethystate(prefix = "network", mode = "persistent")]pub struct NetworkState { #[amestate(default = "127.0.0.1".to_string())] pub host: String,
#[amestate(default = 8080)] pub port: u16,}let mut state = NetworkState::load_with(&store)?;
// Direct field mutationstate.port = 9090;state.save_lazy()?; // debounced background flushstate.save()?; // immediate flush
// Block mutation — immediate flushstate.mutate(|d| { d.host = "0.0.0.0".to_string(); d.port = 443;})?;
// Block mutation — debounced background flushstate.mutate_lazy(|d| { d.host = "0.0.0.0".to_string(); d.port = 443;})?;Reactive Maps
Section titled “Reactive Maps”ReactiveMap<K, V> is a persistent dynamic collection where each entry is stored as an individual key.
#[derive(Debug, Clone, Serialize, Deserialize, Default, AmeType)]pub struct AlertThresholds { pub warning: u64, pub critical: u64,}
#[amethystate(prefix = "sys")]pub struct SystemSettings { #[amestate(default = { "cpu": AlertThresholds { warning: 70, critical: 90 }, "mem": AlertThresholds { warning: 80, critical: 95 } })] pub limits: ReactiveMap<String, AlertThresholds>,}let state = SystemSettings::new()?;
// Insert or updatestate.limits().set_or_create("gpu".into(), &AlertThresholds { warning: 60, critical: 85 })?;
// Lookuplet cpu = state.limits().get(&"cpu".into())?;
// Iteratefor (key, val) in state.limits().entries()? { println!("{key}: {val:?}");}
// Subscribe to any changelet _sub = state.limits().subscribe_any(|change| { println!("{change:?}");});Derived pipelines
Section titled “Derived pipelines”Pipelines derive a value from one or more reactive fields. They recompute automatically when any input changes.
use amethystate::IntoPipeline;
let address = (state.host(), state.port()) .pipe() .map(|(host, port)| format!("{host}:{port}"));
println!("{}", address.get()); // "127.0.0.1:8080"let mut scope = ReactiveScope::new();
address.subscribe(|addr| { println!("address changed: {addr}");}).watch(&mut scope);