Skip to content

Defining Structs

The #[amethystate] macro transforms a plain Rust struct into a persistent state container.

#[amethystate(prefix = "network", version = 1, mode = "reactive", as_root)]
pub struct NetworkState { ... }
AttributeTypeDescription
prefixStringNamespace path in the store. Required for root structs.
versionu32Schema version for migrations. Defaults to 0.
modeStringCode generation mode: "reactive" (default), "persistent", or "both".
as_rootflagIf specified, fields are written directly to the store root without a namespace.

Structs without prefix are nested components, intended to be embedded in other structs via nested.

Field attributes are optional. A field with no #[amestate] annotation uses Default::default() as its value and the field name as its storage key.

#[amethystate(prefix = "app")]
pub struct AppState {
pub counter: u32, // no annotation — uses Default::default(), stored as "app.counter"
#[amestate(default = 8080)]
pub port: u16,
}
AttributeTypeDescription
defaultExprInitial value on first run. If omitted, uses Default::default().
nestedflagMarks field as an embedded #[amethystate] struct.
volatileflagIn-memory only. Never read from or written to the store. Resets to default on every restart.
export_mutflagAllows this field to be mutated via lookup from other structs.
keyStringOverrides the storage key. Defaults to the field name.
lookupStringLinks to a field in a parent struct. Supports dot-notation.
lookup_nodeStringLinks to a nested struct node in a parent struct.
parentTypeThe source struct for lookup or lookup_node.

The #[derive(AmeType)] macro is used to implement schema tracking for plain Rust structs (e.g., custom data types used as fields inside your #[amethystate] containers).

It calculates a unique compile-time TYPE_HASH representing the structure of your data.

#[derive(Debug, AmeType)]
pub struct CustomEndpoint {
pub host: String,
pub port: u16,
}

Volatile fields live in memory only and reset to their default on every restart. Useful for transient UI state that should not persist.

#[amethystate(prefix = "app")]
pub struct AppState {
#[amestate(default = 8080)]
pub port: u16,
#[amestate(default = false, volatile)]
pub loading: bool, // always starts as false, never written to disk
}

Structs without a prefix are components — they have no storage namespace of their own and are embedded into a parent struct via nested. The parent’s prefix is prepended to all nested fields.

#[amethystate]
pub struct DatabaseConfig {
#[amestate(default = "localhost".to_string())]
pub host: String,
}
#[amethystate(prefix = "sys")]
pub struct SystemSettings {
#[amestate(nested)]
pub db: DatabaseConfig, // stored as "sys.db.host"
}

Fields can share storage with fields in another struct via lookup. References are verified at compile time — a wrong field name or type mismatch is a compile error.

#[amethystate(prefix = "net")]
pub struct NetworkState {
#[amestate(default = 8080, export_mut)]
pub port: u16,
#[amestate(default = "127.0.0.1".to_string())]
pub host: String,
}
#[amethystate(prefix = "ui")]
pub struct UiState {
// Read-write link
#[amestate(lookup = "port", parent = NetworkState, export_mut)]
pub proxy_port: u16,
// Read-only link
#[amestate(lookup = "host", parent = NetworkState)]
pub proxy_host: String,
}

Compile-time guarantees:

  • Wrong field name → no associated item named '__schema_field_porrt'
  • Wrong type → TypeCheck<String> is not implemented for ReadOnly<u16>
  • Writing a read-only link → no method named 'perform_set' found for ReadOnly<T>

lookup_node links an entire nested struct instead of a single field:

#[amethystate(prefix = "ui.inspector")]
pub struct InspectorState {
#[amestate(lookup_node = "db", parent = SystemSettings)]
pub db_view: DatabaseConfig,
// accessed as state.db_view().host().get()
}

By default, all fields are stored under the struct’s prefix. With as_root, fields are written directly to the store root with no namespace — the same layout that confy produces.

#[amethystate(mode = "persistent", as_root)]
pub struct AppConfig {
#[amestate(default = "legacy".to_string())]
pub name: String,
#[amestate(default = false)]
pub comfy: bool,
}

This produces a file like:

name = "legacy"
comfy = false

The primary use case is coexistence with or migration from an existing confy-managed file. See Migrating from confy.