Opening a struct your own way
Every struct with a place of its own implements two traits. Schema opens it
exactly as declared: the fields read, their rules run, the place claimed. Open
is how the rest of the program opens it, and unless the struct says otherwise
it is Schema and nothing more. Code that opens structs it does not name asks
for Open:
fn opened<S: Open>(store: &Store) -> S { S::new_with(store)}open = manual
Section titled “open = manual”open = manual is for a struct whose opening is more than its declaration - a
value worked out from the machine it runs on, a check against something the
application knows. The macro then writes no Open, and the struct’s author
does, starting from Schema:
#[amethystate(prefix = "agent", open = manual)]pub struct AgentSettings { #[amestate(default = 0u32)] pub workers: u32,}
impl Open for AgentSettings { fn try_new_with(store: &Store) -> Result<Self, OpenStruct> { let machine = store.context().require::<Machine>(); let settings = <Self as Schema>::open(store)?;
if settings.workers().get() == 0 { settings.workers().set(machine.cores); }
Ok(settings) }}The author writes try_new_with, the door that answers with a Result;
new_with, which panics with what it answered, comes with the trait. What the
struct needs comes from store.context(), the same values a declared
rule is handed, and require panics naming a
value nobody gave - a store built without it is built wrong the same way on
every run.
An invariant between fields
Section titled “An invariant between fields”A field’s rule sees one value and none of its siblings, and the struct is the
place its fields are kept rather than a value with a rule of its own. So an
invariant between fields goes here. The hand-written Open sees every field at
once, after each has been through its own rule, and puts them right the way a
rule would:
#[amethystate(prefix = "window", open = manual)]pub struct Window { #[amestate(default = 400u32)] pub min: u32,
#[amestate(default = 1600u32)] pub max: u32,}
impl Open for Window { fn try_new_with(store: &Store) -> Result<Self, OpenStruct> { let window = <Self as Schema>::open(store)?;
if window.min().get() > window.max().get() { window.max().set(window.min().get()); }
Ok(window) }}It runs once, where the struct is opened. A write to min afterwards goes
through min’s own rule and through nothing that knows about max; two fields written
from two threads are a limit the
Durability page sets out.
The only way in
Section titled “The only way in”The hand-written Open is the only way in. new_with and try_new_with on
the struct, load_with and try_load_with on a persistent one and
AmeStateSlice all go through it, so no caller opens the struct past it by
accident. That is also why the impl itself starts from <Self as Schema>::open
and never from Self::try_new_with: the second is this very Open, and would
call itself. And a struct that says
open = manual has no new() or load() over the global store: reaching for
the global store is a compile error rather than something a review has to
catch.
open belongs to a struct with a place of its own. A nested struct is opened
by the struct holding it, so the macro refuses open on one.