When a value will not read
Three moments, each with its own answer: a struct opening over bytes that will not decode, a key deleted under a live field, and a change arriving that will not decode. A value that decodes perfectly and is still nonsense is a different matter, and Rules is where it is answered.
Opening
Section titled “Opening”A declared path holding something that will not decode into the field’s type
fails construction and names the path. That is Refuse, the default: new_with
panics with the path and what the codec said, and try_new_with hands the same
back as an Err. UseDefault is for the application that has to start anyway:
the field takes its declared default, the stored value stays on disk for
somebody to fix, and try_get answers Err
from construction until a change decodes.
#[amethystate(prefix = "mixed", on_unreadable = UseDefault)]pub struct Mixed { #[amestate(default = 8080u16)] pub port: u16,
#[amestate(default = "".to_string(), on_unreadable = Refuse)] pub licence: String,}A field may tighten what its struct wrote. Above, the settings open with a
broken port, and a licence that will not read stops the whole thing.
Refuse on the struct with UseDefault on a field is a compile error naming
the field. A nested struct inherits its holder’s answer, tightens it the same
way, and is checked against the holder while it compiles.
Where nothing said, the store does. What a field says wins over what its struct says, and the struct over what the store was opened with — so an application says once what it wants of everything that had no opinion, and every declaration above stands untouched: Opening a store.
A failed construction says which way it failed. try_new_with hands back an
OpenStruct - the ways a constructor can fail and no others - so a caller
tells one from another by its variant rather than by reading the sentence:
match Strict::try_new_with(&store) { Ok(_) => {} Err(OpenStruct::WillNotRead { at, why }) => eprintln!("{at} is unreadable: {why}"), Err(OpenStruct::Taken(taken)) => { eprintln!("{} already holds {}", taken.held_by, taken.at) } Err(other) => return Err(other.into()),}Under UseDefault nothing fails, and the same place and reason arrive through
the field instead - as a Disagreement, which is what the field and the store
do not agree about rather than a failure of the asking:
let held = match state.port().try_get() { Ok(port) => port, Err(no) => { match no.reason { Reason::WillNotRead(said) => eprintln!("{} will not decode: {said}", no.at), Reason::Occupied(said) => eprintln!("{} was already taken: {said}", no.at), _ => eprintln!("{} is not what the store has", no.at), }
state.port().get() }};Bytes that would not decode arrive as WillNotRead with the codec’s own
sentence, and a field that never got to write its default because the store
already held something arrives as Occupied. A field whose store has closed
answers Reason::Closed and says the value is the last one it heard. And
Reason::NotWrittenBack is a rule that put a
stored value right while the store would not take the correction.
Reason is #[non_exhaustive] - it is a list of what a field can be found
disagreeing about, and it grows - so a match over it keeps a _ arm. The
sets do not, and Errors is why.
A key deleted under a live field
Section titled “A key deleted under a live field”The field goes on reporting what it last held: that is what was on screen a
moment ago, and the declared default is a compile-time guess. UseDefault asks
for the guess:
#[amethystate(prefix = "mixed_delete")]pub struct MixedDelete { #[amestate(default = 800u32)] pub width: u32,
#[amestate(default = 600u32, on_delete = UseDefault)] pub height: u32,}A live change that will not decode
Section titled “A live change that will not decode”The field keeps the last value the store agreed with and no subscriber is
called. try_get reports it, and clears itself as soon as a change decodes.
There is nothing to declare here.