[−][src]Crate snec
Configuration system with compile-time field lookup and modification notifications.
Overview
Snec is a configuration system focused on compile-time guarantees and a way of notifying a running system that a configurable value changed. Most of its power is implemented via macros, which is why those are exported by default.
While no built-in serialization support is provided, the architecture by itself is serialization-agnostic — using Serde and Snec for the same config table structure will work just fine.
Snec's architecture consists of those key components:
- Config table — the structure which contains the configuration data for the program. Config tables implement the
Get
trait to access its fields, which allows them to hand outHandle
s to its fields. Handles ensure that the assigned receiver gets notified when the field changes, unless it's explicitly prompted to perform a silent modification. - Entry — an uninhabited type (type with no possible values) implementing the
Entry
trait, representing an identifier for a field inside of a config table. - Receiver — type implementing the
Receiver
trait which will receive notifications whenever a entry in a config table it's interested in is modified.
Basic example
use snec::{ConfigTable, Entry, GetExt as _}; use std::time::{SystemTime, Duration}; #[derive(ConfigTable)] struct MyConfigTable { #[snec] when: SystemTime, #[snec] who: String, #[snec] in_which_country: String, } let mut config_table = MyConfigTable { when: SystemTime::UNIX_EPOCH + Duration::from_secs(566_200_800), who: "Jeremy".to_string(), in_which_country: "USA".to_string(), }; // To access the fields of our config table, we need to use the get_handle method from // the GetExt trait (which is a nicer way to use the Get trait). The `entries` part is // a module generated by the `#[derive(ConfigTable)]`. In most cases, it's desirable // to reexport the contents of the module in a public module with a different name and // some documentation, or simply in the containing module if you want the entry // identifiers to be in the same module as the config table. let mut handle = config_table.get_handle::<entries::InWhichCountry>(); // After we got the handle, we can use it to get a // mutable reference to the field and modify it: { let mut in_which_country = handle.modify(); *in_which_country = "Britain".to_string(); } // The reason why we put that in a scope and why we had to do this entire two-step process // is because otherwise we'd implicitly avoid notifying any receivers, which is something // that we'll look into in the next example. Since we don't have any, it won't really // hurt if we did this as well: { let in_which_country = handle.modify_silently(); *in_which_country = "Australia".to_string(); }
Using receivers:
use snec::{ConfigTable, Receiver, Entry, GetExt as _}; use std::time::{SystemTime, Duration}; #[derive(ConfigTable)] #[snec(receiver = "MyReceiver::new -> MyReceiver")] struct MyConfigTable { #[snec] which_year: i64, #[snec(entry("snec::EmptyReceiver::new -> snec::EmptyReceiver"))] why: String, #[snec] random_integer_that_i_like: u128, } struct MyReceiver; impl MyReceiver { fn new() -> Self { // Since the #[derive(ConfigTable)] can only call functions (no arguments, return // value implements Receiver) to get a receiver, we have to do this. In a future // version, in-place closures might become supported. MyReceiver } } impl Receiver<entries::RandomIntegerThatILike> for MyReceiver { fn receive(&mut self, new_value: &u128) { println!("My integer has been changed to {}!!", new_value) } } impl Receiver<entries::WhichYear> for MyReceiver { fn receive(&mut self, new_value: &i64) { println!("Resceduled to {}", new_value) } } let mut config_table = MyConfigTable { which_year: 1987, why: "Accident".to_string(), random_integer_that_i_like: 687_800, }; // Now we have receivers which will immediately react to any changes in the values: let mut handle = config_table.get_handle::<entries::WhichYear>(); { let mut which_year = handle.modify(); *which_year = 1983; } // When the scope ends, the `which_year` guard is dropped and the receiver is informed.
Re-exports
pub extern crate snec_macros as macros; |
Structs
EmptyReceiver | A receiver which does nothing when notified. |
FnReceiver | A receiver which calls a closure when notified. |
Handle | A handle to a config entry value which is being watched by a receiver. |
IterReceiver | A receiver which creates an iterator from a reference to the contained value and notifies all items which the iterator produces. |
ModificationScope | A drop guard for modifying data bahind a |
Traits
Entry | Trait for type-level identifiers for config entries. |
Get | Trait for getting handles to fields in config tables. |
GetExt | A convenience trait for using turbofish syntax to get handles to fields in config tables. |
Receiver | Trait for types which wish to be notified when the specified configuration table entry changes. |
Derive Macros
ConfigTable | Generates necessary trait implementations to use a struct as a configuration table. |