#table #field #receiver #re-exports #macro #system #snec

macro snec_macros

Macros for the Snec crate, intended to be used via Snec's reexports

2 releases (1 stable)

1.0.0 Nov 8, 2020
0.1.0 Aug 26, 2020

#24 in #receiver


Used in snec

MIT/Apache

35KB
807 lines

Snec

Crates.io Docs.rs Build Status

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 out Handles 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_to::<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(
    // Any expression can be used in the braces. After the colon, the type is supplied.
    receiver({MyReceiver}: MyReceiver)
)]
struct MyConfigTable {
    #[snec]
    which_year: i64,
    #[snec(entry, receiver({snec::EmptyReceiver}: snec::EmptyReceiver))]
    why: String,
    #[snec]
    random_integer_that_i_like: u128,
}

struct 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_to::<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.

Dependencies

~1.5MB
~37K SLoC