Macro v9::decl_table[][src]

macro_rules! decl_table {
    (
        $(#[doc = $doc:literal])*
        $(#[row::$row_meta:meta])*
        $vis:vis struct $name:ident {
            $(
                $(#[$cmeta:meta])*
                pub $cn:ident: $cty:ty,
            )*
        }
    ) => { ... };
    (
        $(#[doc = $doc:literal])*
        $(#[row::$row_meta:meta])*
        #[raw_index($raw:ty)]
        $vis:vis struct $name:ident {
            $(
                $(#[$cmeta:meta])*
                pub $cn:ident: $cty:ty,
            )*
        }
        // FIXME: `in mod $in_mod:tt`
    ) => { ... };
}

Defines a table. This is the most important item in the crate!

Usage

This macro is called decl_table!, but it’s nicer to use it as #[v9::table]. This spares you some indentation, and lets IDEs do “jump to definition”.

// Declare a couple tables.
// Note the snake_casing. The table macro actually epxands this into a module.
#[v9::table]
pub struct cheeses {
    pub quantity: f64,
    // NOTE: You should generally use absolute paths. You may get weird errors otherwise. :(
    pub warehouse: crate::warehouses::Id,
    pub stinky: bool,
}

#[v9::table]
pub struct warehouses {
    pub coordinates: (i32, i32),
    pub on_fire: bool,
}

fn main() {
    // We create a new Universe. The Universe holds everything!
    use v9::prelude::Universe;
    let mut universe = Universe::new();

    // It doesn't know about the tables until we register them.
    use v9::prelude::Register;
    cheeses::Marker::register(&mut universe);
    warehouses::Marker::register(&mut universe);

    // Let's write a closure to print out the inventory.
    let print_inventory = |cheeses: cheeses::Read, warehouses: warehouses::Read| {
        println!("  Warehouses:");
        for id in warehouses.iter() {
            println!("    {:?}", warehouses.ref_row(id));
        }
        println!("  Cheeses:");
        for id in cheeses.iter() {
            println!("    {:?}", cheeses.ref_row(id));
        }
    };
    // The kernel holds our closure, and keeps track of all of the arguments it requires.
    use v9::kernel::Kernel;
    let mut print_inventory = Kernel::new(print_inventory);
    println!("An empty inventory:");
    universe.run(&mut print_inventory);

    // If we don't care allocation, you can use kmap. It reduces noise.
    // Let's use it add some things:
    universe.kmap(|mut warehouses: warehouses::Write, mut cheeses: cheeses::Write| {
        let w0 = warehouses.push(warehouses::Row {
            coordinates: (1, 2),
            on_fire: true,
        });
        let w1 = warehouses.push(warehouses::Row {
            coordinates: (2, 4),
            on_fire: false,
        });
        let w2 = warehouses.push(warehouses::Row {
            coordinates: (4, 2),
            on_fire: true,
        });
        cheeses.reserve(30);
        for wid in &[w0, w1, w2] {
            for _ in 0..10 {
                cheeses.push(cheeses::Row {
                    quantity: 237.0,
                    warehouse: *wid,
                    stinky: true,
                });
            }
        }
    });
    println!("A non-empty inventory:");
    universe.run(&mut print_inventory);
    // But what about those warehouses that are on fire?
    universe.kmap(|list: &mut warehouses::Ids, mut on_fire: warehouses::edit::on_fire| {
        let mut have_extinguisher = true;
        for wid in list.removing() {
            if on_fire[wid] {
                if have_extinguisher {
                    have_extinguisher = false;
                    on_fire[wid] = false;
                } else {
                    wid.remove();
                }
            }
        }
    });
    // v9 has ensured data consistency.
    // The burnt cheese has been destroyed.
    println!("A diminished inventory:");
    universe.run(&mut print_inventory);
    universe.kmap(|cheeses: cheeses::Read| {
        assert_eq!(
            20,
            cheeses.iter().count(),
        );
    });
}

Output

OUTPUT EXAMPLE

Details

There’s several things to be aware of.

  1. Naming. The item name should be snake case (it becomes a module), and plural. The names of columns should be singular, because they will be used like students.mailing_address[student_id]. (Unless the element itself is plural, eg if students.known_aliases[student_id] is a Vec<String>.)
  2. The macro syntax kind of looks like a struct… but it very much is not.
  3. Type paths should be absolute, not relative.
  4. The “struct”’s visiblity may be anything, but the fields are always pub.
  5. Each column must have a unique element type. A table with columns age: u64, income: u64 will not work. You can wrap the structs in a newtype. (I have created the crate new_units to help cope with this.) Or if you don’t care about memory access patterns you can combine the columns into a single Array Of Structs column.

Meta-Attributes

There are certain meta-attributes that may be placed on the “struct”. Due to macro_rules silliness, they must be given in the order listed here:

  1. Documentation. It is placed on the generated module.
  2. #[row::<meta>]* Passes meta-attributes to the generated struct Row; eg #[row::derive(serde::Serialize))]. #[row::derive(Clone, Debug)] is always provided. (If your type is inconvenient to clone, consider wrapping it in an Arc, or something that panics.)
  3. #[raw_index(u32)]. Defines the type used to index. The default is u32. Must be Raw. The last index is generally considered to be ‘invalid’.

Any attributes on the columns will be passed as-is to the fields on Row.

Example

v9::decl_table! {
    /// Some of our many fine cheeses!
    #[row::derive(serde::Serialize)]
    #[row::doc = "Why does our cheese keep catching on fire!??"]
    #[raw_index(u8)]
    pub struct cheeses {
        pub quantity: u64,
        /// P. U.!
        pub stinky: bool,
        #[serde(skip)]
        pub on_fire: Option<bool>,
    }
}