ixa/macros/define_global_property.rs
1/// Defines a global property with the following parameters:
2/// * `$global_property`: Name for the identifier type of the global property
3/// * `$value`: The type of the property's value
4/// * `$validate`: A function (or closure) that checks the validity of the property and returns
5/// `Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>` (optional)
6///
7/// Validator code is client code, so it should create and box its own error values.
8/// Ixa wraps any returned error in
9/// [`IxaError::IllegalGlobalPropertyValue`](crate::error::IxaError::IllegalGlobalPropertyValue)
10/// when the property is set or loaded.
11#[macro_export]
12macro_rules! define_global_property {
13 ($global_property:ident, $value:ty, $validate: expr) => {
14 #[derive(Copy, Clone)]
15 pub struct $global_property;
16
17 impl $crate::global_properties::GlobalProperty for $global_property {
18 type Value = $value;
19
20 fn id() -> usize {
21 // This static must be initialized with a compile-time constant expression.
22 // We use `usize::MAX` as a sentinel to mean "uninitialized". This
23 // static variable is shared among all instances of this concrete item type.
24 static INDEX: std::sync::atomic::AtomicUsize =
25 std::sync::atomic::AtomicUsize::new(usize::MAX);
26
27 // Fast path: already initialized.
28 let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
29 if index != usize::MAX {
30 return index;
31 }
32
33 // Slow path: initialize it.
34 $crate::global_properties::initialize_global_property_id(&INDEX)
35 }
36
37 fn new() -> Self {
38 $global_property
39 }
40
41 fn name() -> &'static str {
42 let full = std::any::type_name::<Self>();
43 // Splitting any string, including an empty one, always yields at least one segment.
44 full.rsplit("::").next().unwrap()
45 }
46
47 fn validate(
48 val: &$value,
49 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
50 $validate(val)
51 }
52 }
53
54 $crate::paste::paste! {
55 $crate::ctor::declarative::ctor!{
56 #[ctor(unsafe)]
57 fn [<$global_property:snake _register>]() {
58 let module = module_path!();
59 // Splitting any module path always yields at least one segment.
60 let mut name = module.split("::").next().unwrap().to_string();
61 name += ".";
62 name += stringify!($global_property);
63 let _ = <$global_property as $crate::global_properties::GlobalProperty>::id();
64 $crate::global_properties::add_global_property::<$global_property>(&name);
65 }
66 }
67 }
68 };
69
70 ($global_property: ident, $value: ty) => {
71 $crate::define_global_property!($global_property, $value, |_| { Ok(()) });
72 };
73}