Macro Cookbook
This chapter is a quick map of the macro surface. The goal is not to replace the deeper chapters, but to make the everyday question easy:
"I know roughly what I want to do. Which macro should I reach for?"
The macros fall into three layers:
- Encoding definition:
attributes! - Fact construction:
entity! - Query construction:
find!,exists!,pattern!,pattern_changes!,and!,or!,temp!
Define attributes with attributes!
Use attributes! to declare typed attributes once,
then reuse them everywhere else.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::{GenId, ShortString}; attributes! { /// A person's display name. "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; /// Another person this person knows. pub friend: GenId; } } assert_ne!(social::name.id(), social::friend.id()); }
The three declaration forms differ in what determines identity:
"HEX_ANCHOR" as name: Encodingderives the id from the stable anchor and encoding. This is the safe default for shared schemas: renaming the Rust binding is free, while changing the encoding creates a different attribute.name: Encodingderives the id from the name and encoding. It is convenient when the source name itself is the shared identifier."HEX_ID" unsafe as name: Encodinguses the literal id without incorporating the encoding. Reserve it for compatibility with an already-published id; the caller must ensure the type still matches the stored rows.
Reach for this macro when:
- you are defining a namespace or encoding module
- you want attributes with stable ids and inline encodings
- you want doc comments to become attribute metadata
If you already have attributes, you usually do not need attributes! in the
rest of the code you are writing.
Build facts with entity!
Use entity! when you want to create tribles for one
entity.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let alice = fucid(); let facts = entity! { &alice @ social::name: "Alice", }; assert_eq!(facts.root(), Some(alice.id)); assert_eq!(facts.len(), 1); }
If you omit the entity id, entity! derives one deterministically from the
facts. It encodes each fact as a 64-byte NIL || attribute || value row,
sorts and deduplicates the rows, hashes their complete contiguous byte
sequence with BLAKE3, and uses the final 16 digest bytes as the entity id.
It then fills that id into every row before constructing the TribleSet.
Field order and duplicate repeated values therefore do not affect identity;
an absent optional field contributes no row.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let facts = entity! { _ @ social::name: "Alice" }; assert!(facts.root().is_some()); assert_eq!(facts.len(), 1); }
The macro also supports optional and repeated fields:
let aliases = ["Al", "A."];
let maybe_nickname = Some("Ace");
let facts = entity! { &alice @
social::name: "Alice",
social::nickname?: maybe_nickname,
social::alias*: aliases,
};
Reach for this macro when:
- you are constructing new data
- you want optional/repeated attribute ergonomics
- you want a deterministic derived id for a value object
Match facts with pattern!
Use pattern! to turn trible-shaped structure into a
query constraint.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::{GenId, ShortString}; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; "B74AA63539354CDA47F387A4C3A8D54C" as pub friend: GenId; } } let mut kb = TribleSet::new(); let alice = fucid(); let bob = fucid(); kb += entity! { &alice @ social::friend: &bob }; kb += entity! { &bob @ social::name: "Bob" }; let results: Vec<Id> = find!( friend: Id, pattern!(&kb, [ { alice.id @ social::friend: ?friend }, { ?friend @ social::name: "Bob" } ]) ).collect(); assert_eq!(results, vec![bob.id]); }
Inside a pattern:
?namerefers to a query variable from the surrounding query_?nameintroduces a local helper variable scoped to that pattern- literal expressions become equality constraints automatically
Use pattern! when you are querying the current contents of a TribleSet,
Checkout, or another pattern-capable source.
Query for results with find!
Use find! when you want rows back.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let alice = fucid(); kb += entity! { &alice @ social::name: "Alice" }; let names: Vec<Inline<_>> = find!( name: Inline<_>, pattern!(&kb, [{ _?person @ social::name: ?name }]) ).collect(); let first: &str = names[0].try_from_inline().unwrap(); assert_eq!(first, "Alice"); }
There are three common shapes:
find!(value, constraint)for one projected variable as a bare valuefind!((a, b), constraint)for tuplesfind!((), constraint)when you want to count satisfying assignments without projecting any of them (reach forexists!if you only need yes/no)
find! heads have BAG semantics: one row per complete binding, with the head
selecting which variables come back. Hidden variables introduced by temp! or
_?name still multiply the result — an entity proved by eight witnesses is
emitted eight times, and the empty head returns one () per satisfying
assignment. Deduplicate on your side (collect::<HashSet<_>>()), or use
exists! for the inner condition so the fan-out is never enumerated.
Typed projections happen in the head:
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let alice = fucid(); kb += entity! { &alice @ social::name: "Alice" }; let ids: Vec<_> = find!( person: Id, pattern!(&kb, [{ ?person @ social::name: "Alice" }]) ).collect(); assert_eq!(ids, vec![alice.id]); }
Use ? on a projected variable when you want conversion failures as
Result<T, E> instead of dropping the row.
Ask existence questions with exists!
Use exists! when you only need yes/no.
#![allow(unused)] fn main() { use triblespace::prelude::*; mod social { use triblespace::prelude::*; use triblespace::prelude::inlineencodings::ShortString; attributes! { "A74AA63539354CDA47F387A4C3A8D54C" as pub name: ShortString; } } let mut kb = TribleSet::new(); let bob = fucid(); kb += entity! { &bob @ social::name: "Bob" }; let has_bob = exists!( pattern!(&kb, [{ _?person @ social::name: "Bob" }]) ); assert!(has_bob); }
You can also keep the typed-head form when the projection itself matters to the check:
let has_name = exists!(
(name: Inline<_>),
pattern!(&kb, [{ ?person @ social::name: ?name }])
);
Use exists!(constraint) for pure existence checks instead of
find!((), constraint).next().is_some().
Match only new results with pattern_changes!
Use pattern_changes! for incremental
queries: matches are allowed to join against the full current state, but at
least one contributing trible must come from the change set.
for (work,) in find!(
(work: Inline<_>),
pattern_changes!(&full, &delta, [
{ ?work @ literature::author: &shakespeare }
])
) {
// process only newly introduced matches
}
Reach for this macro when:
- you already have
fullanddelta - you want monotonic incremental processing
pattern!would re-emit old matches every time
See Incremental Queries for the full workflow.
Combine constraints with and! and or!
Use and! when every clause must hold:
find!(
(friend: Inline<_>),
and!(
pattern!(&kb, [{ ?person @ social::name: "Alice" }]),
pattern!(&kb, [{ ?person @ social::friend: ?friend }])
)
)
Use or! when any branch may hold:
find!(
(alias: Inline<_>),
or!(
pattern!(&kb, [{ ?person @ social::nickname: ?alias }]),
pattern!(&kb, [{ ?person @ social::name: ?alias }])
)
)
or! branches must mention the same variable set.
Introduce helpers with temp!
Use temp! when you need a fresh variable only inside a
sub-expression.
find!(
(person: Inline<_>),
temp!((friend), and!(
pattern!(&kb, [{ ?person @ social::friend: ?friend }]),
pattern!(&kb, [{ ?friend @ social::name: "Bob" }])
))
)
This is useful when the helper participates in joins but should not be
projected. When the helper lives entirely within a single pattern!, prefer a
_?var placeholder: it enforces equality across its occurrences inside that
pattern without being projected.
Which macro should I use?
If you are:
- defining encodings: use
attributes! - building facts for one entity: use
entity! - matching trible structure: use
pattern! - matching only newly added results: use
pattern_changes! - asking for rows back: use
find! - asking for a boolean: use
exists! - requiring all clauses: use
and! - allowing alternatives: use
or! - introducing a fresh helper variable: use
temp!(or_?varinside a singlepattern!)
From here, the best next stops are:
- Query Language for the execution model
- Patterns & Recipes for modeling patterns
- Incremental Queries for
pattern_changes!