Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started: Rust

The single dependency a Rust downstream needs is the umbrella purrdf crate. It re-exports the RDF 1.2 implementation surface at its root and carries every other published crate under a stable module (purrdf::sparql, purrdf::shapes, purrdf::shex, purrdf::gts, purrdf::entail, purrdf::validate, purrdf::slice, purrdf::iri, purrdf::xsd, purrdf::events) — anything a consumer legitimately imports is reachable from purrdf alone, never by reaching into a sub-crate.

cargo add purrdf

The MSRV is Rust 1.96 (stable toolchain only; the workspace is nightly-free by policy).

Build, freeze, serialize, parse

use purrdf::{parse_dataset, serialize_dataset, RdfDatasetBuilder, RdfLiteral, SerializeGraph};

// Build a dataset in interned TermId space.
let mut b = RdfDatasetBuilder::new();
let alice = b.intern_iri("https://example.org/alice");
let knows = b.intern_iri("http://xmlns.com/foaf/0.1/knows");
let bob = b.intern_iri("https://example.org/bob");
let name = b.intern_iri("http://xmlns.com/foaf/0.1/name");
let hi = b.intern_literal(RdfLiteral::simple("Alice"));
b.push_quad(alice, knows, bob, None);
b.push_quad(alice, name, hi, None);
let ds = b.freeze().expect("freeze");

// Serialize to any native codec and parse back, losslessly.
let ttl = serialize_dataset(&ds, "text/turtle", SerializeGraph::Dataset).unwrap();
let back = parse_dataset(&ttl, "text/turtle", None).unwrap();
assert_eq!(back.quad_count(), 2);

The builder→freeze split is the heart of the API: you intern terms and push quads on a mutable RdfDatasetBuilder, then freeze() into an immutable, indexed RdfDataset that every engine (SPARQL, SHACL, ShEx, entailment) evaluates over. See The Interned Dataset IR.

Parsing text directly

let turtle = r#"
    @prefix ex: <https://example.org/> .
    ex:cat ex:says "meow" .
"#;
let dataset = purrdf::parse_dataset(turtle.as_bytes(), "text/turtle", None)
    .expect("valid Turtle");
assert_eq!(dataset.quad_count(), 1);

Malformed input is a typed RdfDiagnostic with a source location where the codec can provide one — never a silent partial parse.

Reaching the other engines

Every engine hangs off the same facade. For example, the zero-dependency IRI leaf and the ShEx schema layer:

let iri = purrdf::iri::parse("https://example.org/cat").expect("valid IRI");
assert_eq!(iri.as_str(), "https://example.org/cat");

let schema = purrdf::shex::parse_shexc(
    "PREFIX ex: <https://example.org/>\nex:Cat { ex:says . }",
    None,
).expect("valid ShExC");

When to depend on a sub-crate instead

Most applications should stop at purrdf. The sub-crates (purrdf-core, purrdf-rdf, purrdf-columnar, purrdf-sparql-eval, purrdf-shapes, purrdf-shex, purrdf-gts, purrdf-entail, purrdf-validate, purrdf-slice, purrdf-iri, purrdf-xsd, purrdf-events) exist for consumers that want exactly one engine — for example, a tool that only needs IRI parsing can depend on the zero-dependency purrdf-iri alone. The crate map is in the repository README.

Every release crate builds cleanly for wasm32-unknown-unknown, so the same Rust code paths work in native and wasm hosts.

Next steps