AMETHYSTATE AMETHYSTATE

State management for Rust GUI apps — persistence, reactivity, and migrations without the boilerplate.

amethystate

Why amethystate

Less glue, more app

Everything you would write yourself — persistence, subscriptions, migrations — handled for you.

No save layer

Writing a field persists it. Debounced flush to disk happens in the background automatically.

Fine-grained reactivity

Subscriptions fire per-field. Compose fields into derived pipelines that recompute automatically.

Built-in migrations

Version your structs, write a migration step. Runs automatically on startup.

Any framework

egui, iced, Dioxus, Leptos, Slint, GPUI, Tauri — each with a dedicated integration.

How it works

One macro, done

Annotate a struct with #[amethystate], set a prefix and a version. Fields persist automatically on every write.

In reactive mode each field is a Field<T> handle — read, write, subscribe. In persistent-only mode fields are plain Rust types — mutate them directly, call save() when done.

Schema drift is detected on startup. If you renamed a field without bumping the version, you get a warning with a diff — not a crash.

#[amethystate(prefix = "network", version = 1)]
pub struct NetworkState {
    #[amestate(default = "127.0.0.1".to_string())]
    pub host: String,
    #[amestate(default = 8080)]
    pub port: u16,
}

let state = NetworkState::new()?;
state.port().subscribe(|p| println!("port → {p}"));
state.port().set(9090)?;

Integrations

Works with your framework

Pick your framework — the integration handles the bridge between amethystate fields and framework-native signals or update loops.

Persistent-only
Immediate
TEA
Reactive
Signal-based
Webview IPC
Entity / Observer

Migrations

Schema evolution, handled

Bump the version, write a step function. The migrator chains them in order and runs on startup before any app code touches the store.

v1

host: String
port: u16

v2

address: String
port: u16
timeout_ms: u64
#[migrate]
#[rename(host => address)]
fn migrate_config_v1_to_v2(
    old: AmeData<v1::Config>,
) -> amethystate::MigrationResult<AmeData<Config>> {
    Ok(AmeData::<Config> {
        address: old.host,
        port: old.port,
        timeout_ms: 5000,
    })
}