rdfx 0.24.0

RDF 1.2 data-structures, traits and utilities: terms (incl. triple terms), triples, quads, interpretations, graphs, datasets, unstar/restar reification helpers.
Documentation

rdfx

Crate Docs MSRV License

Core RDF 1.2 data structures for Rust — terms (triple terms included), triples and quads, graphs and datasets, interpretations and vocabularies, blank node isomorphism, and rdf:reifies round-tripping.

use rdfx::{LocalTerm, triple};

// RDF 1.1: language-tagged literal in object position.
let t = triple!(<"http://example.org/alice"> <"http://xmlns.com/foaf/0.1/name"> "Alice" @ "en");

// RDF 1.2: a triple term as the object (`<<( s p o )>>`).
let annotated = triple!(
    <"http://example.org/bob"> <"http://example.org/says">
    <<( <"http://example.org/s"> <"http://example.org/p"> <"http://example.org/o"> )>>
);
assert!(matches!(annotated.into_object(), LocalTerm::Triple(_)));

Graphs and datasets are generic over one resource type, so pick the representation that fits — indices interned in a vocabulary, or any type opted in with impl_resource!:

use rdfx::{Quad, dataset::{BTreeDataset, TraversableDataset}};

let mut dataset: BTreeDataset<usize> = BTreeDataset::new();
dataset.insert(Quad(1, 0, 2, None));
dataset.insert(Quad(2, 0, 3, Some(9)));

assert_eq!(dataset.quads_count(), 2);

Why this fork

Fork of rdf-types by Timothée Haudebourg. The RDF 1.1 model carries over unchanged; this fork adds:

  • RDF 1.2: triple terms, directional language strings (rdf:dirLangString), and the rdf:reifies annotation model with unstar / restar round-tripping.
  • Hash-backed graph and dataset implementations plus indexed variants, alongside the upstream B-tree ones, and a graph/dataset trait split so traversal, pattern matching and mutation are separate bounds.
  • Rewritten isomorphism: bitmap signature refinement, candidate ordering by set size, augmenting-path bipartite matching instead of greedy matching, and triple-term descent. Two correctness fixes came with it — false positives on blank-free quads, and stale subject/predicate/object index sets after removal.
  • Sealed position-marker traits (IsSubject, IsPredicate, IsObject, IsGraph) turning RDF position constraints into compile-time errors, with generalized-RDF types for when you need to lift them.
  • Performance: SWAR blank id validation, memchr-accelerated Display, itoa and uuid fast paths in blank node generators, allocation removal across isomorphism.
  • Rust 2024 edition, MSRV 1.96, single crate instead of a workspace.
  • Renamed crate: rdf-typesrdfx. Depends on iri-rs (fork of iref) instead of iref, which is the point of the fork.
  • Criterion bench suite (dataset, isomorphism, swar, triple_terms, vocabulary).

Credit and history preserved — see Attribution.

Install

cargo add rdfx

Feature flags

Flag Default Enables
serde Serialize / Deserialize for terms, literals, triples and quads
meta meta module: locspan-tagged values and Strip
contextual Display through a vocabulary via contextual
uuid-generator All UUID blank node generators (v3, v4, v5)
uuid-generator-v3 Name-based (MD5) UUID blank node generator
uuid-generator-v4 Random UUID blank node generator
uuid-generator-v5 Name-based (SHA-1) UUID blank node generator

RDF 1.2

RDF 1.1 data is a syntactic subset — the new variants are simply never constructed. Three additions matter.

Triple terms. A triple used as a term. Strict RDF admits them in object position only; the generalized types lift that restriction. Macro syntax: <<( s p o )>>.

Directional language strings. A language tag plus base direction, datatype rdf:dirLangString. Macro syntax: "value" @ "lang" / "rtl".

use rdfx::{Direction, LiteralType, LocalTerm, Term, triple};

let t = triple!(<"http://example.org/doc"> <"http://example.org/title"> "hello" @ "ar" / "rtl");
let LocalTerm::Named(Term::Literal(literal)) = t.into_object() else { unreachable!() };

assert!(matches!(literal.into_type(), LiteralType::DirLangString { direction: Direction::Rtl, .. }));

The rdf:reifies annotation model. star::unstar_graph rewrites triple terms into RDF 1.1-compatible reified form; star::restar_graph folds them back. The pair round-trips, so 1.2 data survives a 1.1-only channel.

Storage

Storage is uniform: BTreeDataset<R>, HashDataset<R> and their indexed variants hold one R: Resource in every position. LocalTerm — the lexical alphabet — is deliberately not a Resource, because a literal cannot be a subject. Two ways around that:

  • Intern lexical values in a vocabulary and store the handles. Comparison and cloning collapse to integer operations.
  • Wrap the lexical term in a newtype, opt it in with impl_resource!, and supply an Interpretation mapping the wrapper back to IRIs, literals and triple-term components.

Indexed variants carry subject/predicate/object indices for pattern matching; plain variants are smaller and build faster.

Isomorphism

Two RDF graphs are isomorphic when some bijection over blank nodes makes them equal. graph_equivalent / dataset_equivalent answer yes or no; find_bijection_with returns the mapping itself.

use rdfx::{Quad, dataset::{BTreeDataset, isomorphism::dataset_equivalent}};

let mut a: BTreeDataset<usize> = BTreeDataset::new();
a.insert(Quad(1, 0, 2, None));

let mut b: BTreeDataset<usize> = BTreeDataset::new();
b.insert(Quad(1, 0, 2, None));

assert!(dataset_equivalent(&a, &b));

The _with variants take an Interpretation, which is what makes blank nodes — and the interiors of triple terms — visible to the matcher. Without one, resources are opaque and equivalence degrades to set equality.

Vocabularies

A vocabulary maps lexical values onto lightweight handles, so a dataset can store integers instead of IRIs:

use rdfx::{iri, vocabulary::{IndexVocabulary, IriVocabulary, IriVocabularyMut}};

let mut vocabulary: IndexVocabulary = IndexVocabulary::default();
let i = vocabulary.insert(iri!("http://example.org/s"));

assert_eq!(vocabulary.iri(&i), Some(iri!("http://example.org/s")));

Benchmarks

cargo bench

Criterion output: target/criterion/.

MSRV

Rust 1.96 (edition 2024).

Attribution

Original crate: rdf-types by Timothée Haudebourg. Upstream commits are preserved in this repo's history under their original authorship. This fork is RDF 1.2 support, extra storage backends, a rewritten isomorphism engine, and a layer of performance work on top of the original design.

License

Dual-licensed, same as upstream. Pick whichever fits: